helio.loureiro.eng.br
  • Home
  • Unix
  • Linux
  • Blog
  • Python
  • Programação
  • Tudo
  • Suécia
  1. You are here:  
  2. Home

Os artigos mais lidos de 2025

  • Configurando o grafana alloy pra monitorar VMs
  • Configurando traefik com ssh
  • Configurando o teclado Keychron C3 no Linux
  • Parâmetros de compilação pra Go!
  • Acessos de robôs nos logs web

Home

Imprimindo uma caixa em volta do texto em shell

Details
Written by: Helio Loureiro
Category: Shell Scripts
Published: February 26, 2025
Hits: 988
  • bash
  • UI

Nada muito glamoroso. Só um script usando utf-8 pra fazer uma caixinha bonitinha em volta do texto.

  #!/usr/bin/env bash

sizeof() {
    local msg="$1"
    local size=$(echo $msg | wc -L)
    # one space at beginning and other at the end
    size=$((size+2))
    echo $size
}

printchar() {
    local char="$1"
    local nr="$2"
    while [ $nr -gt 0 ]
        do
        echo -ne "$char"
        nr=$((nr-1))
    done
}


printbox() {
    local msg="$1"
    local s=$(sizeof "$msg")

    echo -n "┌"
    printchar "─" $s
    echo "┐"
    echo -e "│ ${msg} │"
    echo -n "└"
    printchar "─" $s
    echo -e "┘\n"
}

message="$@"
printbox "$message"

O código também está publicado no GitHub.

https://github.com/helioloureiro/homemadescripts/blob/master/printbox.sh

Configurando o grafana alloy pra monitorar VMs

Details
Written by: Helio Loureiro
Category: Observability
Published: February 21, 2025
Hits: 1680
  • mimir
  • grafana
  • alloy
  • grafana-agent

Estou inaugurando uma categoria nova, observability, ou... observabilidade na língua pátria. Nunca comentei nada por aqui, mas nos últimos anos em que trabalhei na Ericsson, o fiz dentro do time que mantinha o Prometheus. Prometheus é um conhecido sistema de monitoração dentro de clusters Kubernetes, ou k8s pros já iniciados.

Não era um trabalho glorioso que contribuia pro projeto. Não diretamente. Era mais sobre compilar a partir dos fontes com o sistema oficial da empresa, que era Suse (e talvez ainda seja) e passar por alguns testes em cima dos helm charts que construíamos. Além de algumas verificações de segurança.

Segurança aliás foi uma das poucas coisas que contribuí pro projeto open source. Reportei algumas vulnerabilidades encontradas pelos nossos scanners em alguns pacotes usados. Geralmente em javascript/npm.

O tempo passou, fui demitido, e acabei em outro emprego.

No emprego atual não existe tanta demanda assim por k8s. Até tem alguma coisa, mas o negócio mesmo gira em torno de VMs com Linux. Então nada de Prometheus pra esses casos. Mas existem alternativas. Uma delas é usar um programa "agente" rodando nessas VMs e enviar pra um "Prometheus" os dados ao invés de esperar que um Prometheus real busque o dado, como é o caso dentro de um cluster k8s.

Pra isso instalei o mimir do Grafana. Mimir, aquele deus que cortam a cabeça e Odin a preserva pra ouvir seus conselhos, funciona como um remote write de Prometheus. Em linguagem mais simples, é onde os dados ficam guardados. Um DB dos dados coletados. E é possível fazer o Grafana, aquele dos dashboards, ler esses dados dele.

Eu segui os passos de instalação em VM ao invés de usar k8s pro mimir. É mais fácil pra gerenciar o espaço em disco e aumentar se necessário. E também no cloud provider que usamos, custa mais barato assim. A configuração que fiz é assim em /etc/grafana/mimir.yaml:

  multitenancy_enabled: false
no_auth_tenant: <token>

blocks_storage:
  backend: filesystem
  bucket_store:
    sync_dir: /var/mimir/tsdb-sync
  filesystem:
    dir: /var/mimir/data/tsdb
  tsdb:
    dir: /var/mimir/tsdb

compactor:
  data_dir: /var/mimir/compactor
  sharding_ring:
    kvstore:
      store: memberlist

distributor:
  ring:
    instance_addr: 127.0.0.1
    kvstore:
      store: memberlist

ingester:
  ring:
    instance_addr: 127.0.0.1
    kvstore:
      store: memberlist
    replication_factor: 1

ruler_storage:
  backend: filesystem
  filesystem:
    dir: /var/mimir/rules

server:
  http_listen_address: localhost
  http_listen_port: 9009
  log_level: warn
  grpc_server_max_recv_msg_size: 2147483647
  grpc_server_max_send_msg_size: 2147483647

store_gateway:
  sharding_ring:
    replication_factor: 1

usage_stats:
  enabled: true

limits:
  ingestion_burst_size: 3500000
  ingestion_rate: 100000
  max_global_series_per_user: 100000000
  max_label_names_per_series: 50
  max_query_parallelism: 224
  out_of_order_time_window: "5m"

Essa é a maneira mais simples possível. O token é que será usado em todo lugar pra validar as conexões. De grafana aos cliente alloy. E usando nginx pra gerenciar os certificados SSL e conectar a port 443 com a interna 9009, onde roda o mimir.

Falando dos clientes, esses eu instalei primeiro o grafana-agent, que faz a mesma coisa. Mas a página de configuração já avisava que estava defasado e que era pra migrar pro alloy. Então resolvi fazer isso logo ao invés de esperar parar de funcionar.

A primeira configuração que fiz pro grafana-agent em /etc/grafana-agent.yaml era a seguinte:

  server:
  log_level: info

metrics:
  global:
    scrape_interval: 1m
  wal_directory: '/var/lib/grafana-agent'
  configs:
    # Example Prometheus scrape configuration to scrape the agent itself for metrics.
    # This is not needed if the agent integration is enabled.
    - name: agent
      host_filter: false
      scrape_configs:
        - job_name: agent
          static_configs:
            - targets: ['localhost:9090']
          relabel_configs:
            - source_labels: [__address__]
              replacement: <nome do servidor>
              target_label: instance
              action: replace
      remote_write:
        - url: https://<endereço do mimir>/api/v1/push
          headers:
            X-Scope-OrgID: <token>

integrations:
  agent:
    enabled: true
  node_exporter:
    enabled: true
    include_exporter_metrics: true
    disable_collectors:
      - "mdadm"

É possível ver que dentro de remote_write eu coloco o endereço do mimir em url com REQUEST_URI apontando pra /api/v1/push e que adiciono um header X-Scope-OrgID com o mesmo token pra validar. E isso funcionava.

Mas tive de mudar pro alloy. E a configuração, que usa uma sintaxe própria (e bastante bizarra), é a seguinte:

  root@heliotest1:~# cat /etc/alloy/config.alloy

    prometheus.exporter.unix "company" {
        enable_collectors = ["cpu", "disk", "filesystem", "systemd"]
        cpu  {
                guest = true
                info = true

        }
        disk { }
        filesystem {
                mount_timeout = "3s"
        }
        systemd {
                enable_restarts = true
                start_time = true
        }


}

prometheus.remote_write "production" {
        endpoint {
                url = "http://<endereço do mimir>/api/v1/push"
                headers = {
                        "X-Scope-OrgID" = "<token>",
                }
                queue_config { }
                metadata_config { }
        }
}

// Collect metrics from Kubernetes pods and send them to prod.
prometheus.scrape "first" {
        targets    = prometheus.exporter.unix.company.targets
        forward_to = [prometheus.remote_write.production.receiver]
}

Eu copiei alguns valores do manual de configuração, como o que é visto em filesystem. Efetivamente não vi se faz muita diferença isso ou não.

Feito isso, os agentes alloy começam a enviar os dados pro mimir. Na configuração do mimir é possível ver que eu aumentei alguns parâmetros pra aguentar a quantidade de dados recebidos como grpc_server_max_recv_msg_size/grpc_server_max_send_msg_size.

O passo final é adicionar essa origem no grafana pra gerar as dashboards. A origem tem de escolher como se fosse um prometheus.

Em seguida a configuração.

Note que o endereço do mimir [1] passa a usar a REQUEST_URI /prometheus e que o é preciso adicionar um campo de header X-Org-ID [2] onde estará o valor do token configurado no mimir. Feito isso, basta usar o mimir como se fosse um Prometheus e gerar seus dashboards.

Boa diversão!

Configurando o teclado Keychron C3 no Linux

Details
Written by: Helio Loureiro
Category: Linux
Published: February 17, 2025
Hits: 1402
  • teclado
  • keychron
  • c3
  • teclado mecânico

Eu já tinha comentando que uso teclados da marca Keychron no artigo trabalhando de home-office - atualização de 2021 com teclado Keychron C1, mas o que eu não tinha descrito ainda é que acabei comprando mais de 1 teclado.

Como gostei bastante do teclado com switches brown, mais macios e menos barulhentos que o blue, eu queria experimentar os red. Encontrei uma promoção do modelo K1, wireless, por 70 USD. E com switches red. Só não tinha RGB e os switches eram fixos. Se eu comprasse somente os switches, uma vez que meu teclado C1 é swappable, eu gastaria 60 USD. Então por 10 USD a mais eu teria um teclado novo. E resolvi fechar a compra.

Quando fui apertar pra comprar... aparece uma promoção de um teclado C3 com switches red por 30 USD. A mão conçou, o escorpião no bolso não ficou feliz, mas fui lá e comprei esse outro teclado com a ideia de deixar ele no trabalho. E assim foram 100 USD em teclado que eu nem precisava. Mas isso fica pra outra estória.

Quando recebi os teclados novos, coloquei o K1 em casa e deixei o C3 no trabalho. Na época estava com um laptop da Apple. E tudo funcionava sem problemas.

O tempo passou, e as coisas mudaram. Mudei de emprego. E de laptop como consequência. Mas levei o C3 pra ficar no trabalho, de onde tirei essa foto. Só então percebi que a tecla de menu não funcionava no Linux. E fiquei sem funcionar uns 4 meses. Até que resolvi deixar de ser preguiçoso e corrigir. Achei o artigo abaixo que falava sobre esse teclado no Linux:

https://www.reddit.com/r/Keychron/comments/144dj1c/keychronvia_support_and_linux/

Que por sua vez apontava pra outro artigo:

https://www.reddit.com/r/Keychron/comments/12f3gat/useviaapp_in_linux_ie_via_support_useful_for/

Eu apliquei a solução proposta pra ter o programa pelo browser funcionando pra mapear as teclas.

  
❯ cat /etc/udev/rules.d/99-vial.rules 
KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="3434", ATTRS{idProduct}=="0430", M
ODE="0660", GROUP="users", TAG+="uaccess", TAG+="udev-acl"
  

Reiniciei o udev:

  
$ udevadm trigger
$ udevadm control --reload-rules
  

E o resultado ao reconectar o teclado:

  
Feb 13 13:37:47 thinkpadL14 kernel: usb 1-2.3: new full-speed USB device number 53 using xhci
_hcd
Feb 13 13:37:47 thinkpadL14 kernel: usb 1-2.3: New USB device found, idVendor=3434, idProduct
=0430, bcdDevice= 1.00
Feb 13 13:37:47 thinkpadL14 kernel: usb 1-2.3: New USB device strings: Mfr=1, Product=2, Seri
alNumber=0
Feb 13 13:37:47 thinkpadL14 kernel: usb 1-2.3: Product: Keychron C3 Pro
Feb 13 13:37:47 thinkpadL14 kernel: usb 1-2.3: Manufacturer: Keychron
Feb 13 13:37:47 thinkpadL14 kernel: input: Keychron Keychron C3 Pro as /devices/pci0000:00/00
00:00:14.0/usb1/1-2/1-2.3/1-2.3:1.0/0003:3434:0430.001E/input/input94
Feb 13 13:37:47 thinkpadL14 kernel: hid-generic 0003:3434:0430.001E: input,hidraw1: USB HID v
1.11 Keyboard [Keychron Keychron C3 Pro] on usb-0000:00:14.0-2.3/input0
Feb 13 13:37:47 thinkpadL14 kernel: hid-generic 0003:3434:0430.001F: hiddev0,hidraw2: USB HID
 v1.11 Device [Keychron Keychron C3 Pro] on usb-0000:00:14.0-2.3/input1
Feb 13 13:37:47 thinkpadL14 kernel: input: Keychron Keychron C3 Pro Mouse as /devices/pci0000
:00/0000:00:14.0/usb1/1-2/1-2.3/1-2.3:1.2/0003:3434:0430.0020/input/input95
Feb 13 13:37:47 thinkpadL14 kernel: input: Keychron Keychron C3 Pro System Control as /device
s/pci0000:00/0000:00:14.0/usb1/1-2/1-2.3/1-2.3:1.2/0003:3434:0430.0020/input/input96
Feb 13 13:37:47 thinkpadL14 kernel: input: Keychron Keychron C3 Pro Consumer Control as /devi
ces/pci0000:00/0000:00:14.0/usb1/1-2/1-2.3/1-2.3:1.2/0003:3434:0430.0020/input/input97
Feb 13 13:37:47 silverhand kernel: input: Keychron Keychron C3 Pro Keyboard as /devices/pci0
000:00/0000:00:14.0/usb1/1-2/1-2.3/1-2.3:1.2/0003:3434:0430.0020/input/input98
Feb 13 13:37:47 thinkpadL14 kernel: hid-generic 0003:3434:0430.0020: input,hidraw3: USB HID v
1.11 Mouse [Keychron Keychron C3 Pro] on usb-0000:00:14.0-2.3/input2    
  

Mas nada da tecla funcionar. Continuei lendo os posts até que achei esse aqui:

https://www.reddit.com/r/Keychron/comments/179gp5d/comment/l0dndbf/?utm_source=share&utm_med ium=web3x&utm_name=web3xcss&utm_term=1&utm_content=share_button

tl;dr: basicamente era só usar <Fn>+<Win/Ubuntu Key> pra voltar a ativar. Fácil assim. E agora tenho a tecla funcionando. E sim, eu coloquei um stickerzinho de Ubuntu em cima da tecla.

Parâmetros de compilação pra Go!

Details
Written by: Helio Loureiro
Category: Go
Published: February 14, 2025
Hits: 1363
  • segurança
  • Go!
  • Compilação
  • FOSDEM

Estou assistindo agora algumas apresentações que não pude ver ao vivo durante o FOSDEM 2025. E uma dessas foi sobre como compilar Go! corretamente feita pelo Dimitri John Ledkov.

O palestrante não é programador Go! mas enpacotador pra várias distros. E conhece bem sobre quais parâmetros usar.

Eu alterei meu Makefile do programa negofetch, uma re-escrita em Go! do neofetch que estou fazendo, pra usar as dicas dele.

  
BINARY = negofetch

BUILD_OPTIONS = -modcacherw
#BUILD_OPTIONS += -race
BUILD_OPTIONS += -ldflags="-w -X 'main.Version=$$(git tag -l --sort taggerdate | tail -1)'"
BUILD_OPTIONS += -buildmode=pie
BUILD_OPTIONS += -tags netgo,osusergo
BUILD_OPTIONS += -trimpath

all: $(SOURCES) dependencies $(BINARY)

dependencies:
        go mod tidy

$(BINARY): $(SOURCES)
        env GOAMD64=v2 \
                CGO_ENABLED=1 \
        go build $(BUILD_OPTIONS) .    
  

Olhando pelo govulncheck, que ele também recomenda usar, parece bom.

  
❯ govulncheck -mode=binary negofetch
Scanning your binary for known vulnerabilities...

No vulnerabilities found.

Share feedback at https://go.dev/s/govulncheck-feedback.    
  

Pra quem estiver interessado, esse é o vídeo:



https://fosdem.org/2025/schedule/event/fosdem-2025-4406-build-better-go-release-binaries/

Dirigindo de bunda quente

Details
Written by: Helio Loureiro
Category: Pessoal
Published: January 18, 2025
Hits: 1127
  • inverno
  • carro

Quem me acompanha no Mastodon sabe que eu estava de férias, durante as festas de fim de ano, no Brasil. Enquanto estive lá, deixei meu carro emprestado pra um grande amigo na Suécia.

Quando voltei e peguei o carro, notei algo diferente: minha bunda estava quentinha.

Só olhando no painel que vi que estava ativado o aquecimento do assento. Eu nem sabia que o carro tinha isso. Mas algo maravilhoso durante o inverno.

All we need is lov... bunda quente! All we need is bunda quentinha durante o inverno.

Totais de transporte durante 2024

Details
Written by: Helio Loureiro
Category: Blog
Published: January 02, 2025
Hits: 1055
  • python
  • ciclismo
  • bike
  • pedal
  • google maps
  • ipython

Eu não sei bem o motivo, mas esse fim de ano o Google não mandou mais os dados totalizados de meios de transporte. E não tem mais como pegar os valores pelo browser. Só pelo app.

Eu queria saber quanto pedalei no total durante 2024. Então precisei fazer manualmente uma tabela e cálculo. Chato, mas nada de absurdo.

Os resultados foram os seguintes (dados em Km):

Mês Carro Bicicleta Transporte público Avião Motocicleta Andando
Janeiro 43 - 71 - 11 9
Fevereiro 28 - 182 2577 - 11
Março 124 187 - - - 7
Abril 125 113 33 - - 2
Maio 22 475 - - - 15
Junho 1153 244 33 - - 15
Julho 591 157 47 - - 30
Agosto 220 254 31 - - 7
Setembro 46 157 99 2173 - 25
Outubro 166 135 25 - - 6
Novembro 173 130 116 - - 14
Dezembro 512 113 20 10935 - 6

E fiz um programa em python, dentro do ipython, pra gerar os resultados (e também pra fazer depois essa tabela aqui em html):

  
In [48]: def soma_coluna(coluna):
    ...:     soma = 0
    ...:     for line in lines:
    ...:         p = line.split("|")
    ...:         try:
    ...:             soma += int(p[coluna])
    ...:         except TypeError:
    ...:             pass
    ...:         except ValueError:
    ...:             pass
    ...:     return soma    
  

O resultados totais:

  • Carro: 3203 Km
  • Bicicleta: 1965 Km
  • Transp. Público: 657 Km
  • Avião: 15685 Km
  • Motocicleta: 11 Km
  • Andando: 147 Km

Confesso que fiquei decepcionado. Com 475 Km pedalados em maio, eu achei que ia bater os 3000 Km em 2024. Nem 2500 Km eu cheguei.

No artigo pedal forte de 2023 em dados do Google, é possível ver que também pedalei em torno de 2000 Km. Existe alguma divergência nos dados do Google, porque tenho 2 contas (uma free tier e outra paga), mas não acho que mudaria muita coisa. E não tive paciência de ficar catando dado mês-a-mês da outra conta.

Alguns dados parecem incorretos. Eu por exemplo não andei de moto em janeiro. Esse é o mês em que está tudo de baixo de neve na Suécia. Mas usei a moto pra ir trabalhar em outubro. Então pode ser que o dado da moto seja uma bicicleta na verdade (bem provável), mas e os dados da motocicleta devem ter ido parar na do carro em outubro. Não faria muita diferença nos dados totais.

Atualizando os artigos mais lidos

Details
Written by: Helio Loureiro
Category: Joomla
Published: January 01, 2025
Hits: 980

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...

Details
Written by: Helio Loureiro
Category: Apache
Published: December 26, 2024
Hits: 1181
  • ataque
  • DoS
  • munin
  • monitoração

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

Details
Written by: Helio Loureiro
Category: Blog
Published: December 25, 2024
Hits: 1086

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

Meus votos pra 2025: o ano do Linux no desktop

Details
Written by: Helio Loureiro
Category: Blog
Published: December 25, 2024
Hits: 1175

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

Details
Written by: Helio Loureiro
Category: Linux
Published: December 16, 2024
Hits: 1209
  • zfs
  • swap
  • Ubuntu
  • 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

Details
Written by: Helio Loureiro
Category: Blog
Published: November 27, 2024
Hits: 1344
  • python
  • ffmpeg
  • webcam
  • youtube

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

Details
Written by: Helio Loureiro
Category: Ansible
Published: November 23, 2024
Hits: 1265
  • ansible
  • json

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
  • Fui detonado!
  • Correndo atrás do tempo perdido com Go!
  • Ferramentas modernas de Unix
  • Nem tudo é o que parece...

Page 4 of 37

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

Estatísticas

  • Users 2
  • Articles 485
  • Articles View Hits 3527769

Imagem aleatória