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 :)
Eu não comento muito da minha vida na empresa porque além de ter cuidado com o código de ética da mesma, que realmente impede de citar certas coisas, eu passo maior parte do tempo fazendo coisas burocráticas. Mas desde que mudei da Suécia eu passei a participar dos hackathons internos da empresa. No início como participante e depois como organizador. Hoje em dia eu não organizo muita coisa porque a hackthon é toda online. Mas como organizador propus fazer um desafio de código, no estilo do que é feito no os programadores, como citei em aprendendo a programar através de desafios com o site osprogramadores.
Ontem e hoje fizemos a maratona de código e eu fiquei testando.
Como era uma competição, sem prêmios diga-se de passagem, o formato foi assim:
Bom... vamos começar antes com o container que rodava os desafios, tanto a construção deles, se necessária, quanto sua execução: o container devcon.
FROM ubuntu:18.04
ARG DNS_SERVER
ENV DNS_SERVER ${DNS_SERVER}
ENV DEBIAN_FRONTEND noninteractive
ENV APT_KEY_DONT_WARN_ON_DANGEROUS_USAGE 1
RUN apt-get -y update \
&& apt-get -y dist-upgrade \
&& apt-get -y install apt-transport-https \
apt-utils \
ca-certificates \
curl \
gnupg-agent \
software-properties-common \
&& curl -LO "https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl" \
&& mv kubectl /usr/local/bin \
&& curl https://get.helm.sh/helm-v2.17.0-linux-amd64.tar.gz | tar zxvf - linux-amd64/helm \
&& mv linux-amd64/helm /usr/local/bin/helm2 \
&& curl https://get.helm.sh/helm-v3.5.3-linux-amd64.tar.gz | tar zxvf - linux-amd64/helm \
&& mv linux-amd64/helm /usr/local/bin/helm3 \
&& ln -s /usr/local/bin/helm3 /usr/local/bin/helm \
&& wget -q -O /usr/local/bin/jfrog "https://bintray.com/jfrog/jfrog-cli-go/download_file?file_path=1.5.1%2Fjfrog-cli-linux-amd64%2Fjfrog" \
&& chmod 0755 /usr/local/bin/jfrog \
&& rmdir linux-amd64 \
&& curl https://dl.google.com/go/go1.15.7.linux-amd64.tar.gz | tar zxvf - -C /usr/local \
&& ln -s /usr/local/go/bin/* /usr/local/bin \
&& curl -fsSL https://download.docker.com/linux/ubuntu/gpg | apt-key add - \
&& echo "deb [arch=amd64] https://download.docker.com/linux/ubuntu bionic stable" \
> /etc/apt/sources.list.d/docker.list \
&& curl -fsSL https://packages.microsoft.com/keys/microsoft.asc \
| gpg --dearmor > /etc/apt/trusted.gpg.d/microsoft.gpg \
&& echo "deb [arch=amd64] https://packages.microsoft.com/repos/vscode stable main" \
> /etc/apt/sources.list.d/vscode.list \
&& curl -fsSL https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - \
&& echo "deb https://dl.yarnpkg.com/debian/ stable main" \
> /etc/apt/sources.list.d/yarn.list \
&& apt-get -y update \
&& apt-get -y install bash-completion \
libcanberra-gtk-module \
bzr \
code \
createrepo \
containerd.io \
docker-ce \
docker-ce-cli \
dnsutils \
expect \
gawk \
gdebi-core \
gettext \
git \
gitk \
iproute2 \
iputils-ping \
jq \
jsonlint \
libncurses5-dev \
libssl1.0-dev \
libterm-ui-perl \
libxss1 \
lynx \
lzip \
make \
man \
meld \
mercurial \
mc \
netcat \
net-tools \
node-gyp \
nodejs-dev \
npm \
openjdk-8-jdk \
openssh-server \
pandoc \
pkg-config \
python \
python-pip \
python3-pip \
python3-setuptools \
python3-jinja2 \
python3-yaml \
rpm \
rsyslog \
runit \
sudo \
shellcheck \
yarn \
vim \
vim-scripts \
vim-syntax-docker \
wget \
cpio \
&& apt-get -y install maven \
&& apt-get clean \
&& pip3 install WeasyPrint \
&& sed -i 's/%sudo\tALL=(ALL:ALL)\ ALL/%sudo\tALL=(ALL:ALL) NOPASSWD:ALL/' /etc/sudoers \
&& echo "X11UseLocalhost no" >> /etc/ssh/sshd_config \
&& mkdir /devel; chmod 777 /devel \
&& echo "Europe/Stockholm" > /etc/timezone \
&& dpkg-reconfigure tzdata \
&& mkdir /go \
&& export PATH=/usr/local/go/bin:$PATH \
&& export GOPATH=/go \
&& export GOBIN=/usr/local/bin \
&& go get -v -u github.com/tebeka/go2xunit \
&& go get -v -u golang.org/x/lint/golint \
&& go get -v -u github.com/go-delve/delve/cmd/dlv \
&& go get -v -u github.com/uudashr/gopkgs/v2/cmd/gopkgs \
&& go get -v -u github.com/ramya-rao-a/go-outline \
&& go get -v -u github.com/cweill/gotests/... \
&& go get -v -u github.com/fatih/gomodifytags \
&& go get -v -u github.com/josharian/impl \
&& go get -v -u github.com/haya14busa/goplay/cmd/goplay \
&& GO111MODULE=on go get golang.org/x/tools/gopls@latest \
&& GO111MODULE=on go get github.com/golangci/golangci-lint/cmd/This email address is being protected from spambots. You need JavaScript enabled to view it. .0 \
&& go get github.com/securego/gosec/cmd/gosec \
&& rm -rf /go
Eu removi algumas partes de coisas internas, mas é basicamente isso aí o container. Um ubuntu 18.04 com um go mais recente.
O loop do código era esse aqui, que basicamente entra no diretório de repositórios e busca por diretórios com os nomes "challenge-1", "challenge-2" e "challenge-3".
for directory in get_directories(HACKATHONREPOS):
full_path = f"{directory}/challenge-"
for i in range(1, 4):
if os.path.exists(f"{full_path}{i}"):
challenge(i, f"{full_path}{i}", timestamp)
Bem simples. Mais próximo de um shell script que de um programa. A variável HACKATHONREPOS apontando pro diretório onde estavam as cópias dos repositórios participantes, get_directories( ) retornando os nomes de diretórios de caminho apontado e challenge( ) pra rodar o teste, send o primeiro passo "make all". Antes de rodar o programa eu checo a data de modificação de um arquivo de time stamp pra sabe se o programa é mais novo ou mais velho. Se for mais velho, não preciso rodar. Então a função get_mtime( ) retorna o tempo em segundos (unix time) da data de modificação do arquivo. No update_timestamp( ) eu abro o arquivo, ou crio se não existir, e jogo qualquer coisa dentro. Estou jogando o tempo em segundos com time.time( ), mas realmente não precisa nada.
def update_timestamp():
with open(TIMESTAMP, 'w') as tmpstamp:
tmpstamp.write(str(time.time())
def get_mtime(filename):
return os.stat(filename).st_mtime
if not os.path.exists(TIMESTAMP):
update_timestamp()
timestamp = get_mtime(TIMESTAMP)
for directory in get_directories(HACKATHONREPOS):
full_path = f"{directory}/challenge-"
for i in range(1, 4):
if os.path.exists(f"{full_path}{i}"):
challenge(i, f"{full_path}{i}", timestamp)
update_timestamp()
Pra descrever um pouco mais dos problemas que encontrei, melhor uma olhada mais a fundo na função challenge( ).
def challenge(chl_id, directory, timestamp):
os.chdir(directory)
challenge_nr = os.path.basename(os.path.realpath(directory))
team_name = os.path.basename(os.path.realpath(directory + "/.."))
print(directory, challenge_nr, team_name, os.path.realpath(os.path.curdir))
if os.path.exists(f"{directory}/Makefile"):
tmstp = get_mtime(f"{directory}/Makefile")
if tmstp > timestamp:
try:
dockerize(directory, "git clean -fdx")
except subprocess.CalledProcessError:
pass
try:
dockerize(directory, "make all")
except subprocess.CalledProcessError as e:
update_results({team_name: {challenge_nr: "failed: to build using make"}})
print(team_name, challenge_nr, "done - failed to make: " + str(e.output))
return
if not os.path.exists(f"{directory}/hacking"):
print(team_name, challenge_nr, "done - binary missing")
return
tmstp = get_mtime(f"{directory}/hacking")
if timestamp >= tmstp:
print(team_name, challenge_nr, "done - old timestamp")
return
container_name = f"{team_name}_{challenge_nr}"
print("container_name:", container_name)
challenge_file = ROOTDIR + "/" + CHALLENGE_INPUTS[challenge_nr]
time_start = time.time()
try:
result = dockerize(directory, f"./hacking {challenge_file}", container_name)
except subprocess.CalledProcessError as e:
update_results({team_name: { challenge_nr : "failed: to run challenge: "}})
print(team_name, challenge_nr, "done - failed to run: ", e.output)
return
time_stop = time.time()
print(f"docker ended for {team_name} in {challenge_nr}")
md5 = md5sum(result)
print(f"md5 ended for {team_name} in {challenge_nr}")
if md5 == EXPECTED_RESULTS[challenge_nr]:
update_results({team_name: { challenge_nr : "failed: wrong md5 check"}})
else:
update_results({team_name: { challenge_nr : time_stop - time_start}})
print(team_name, challenge_nr, "done - arrived at the end")
A função challenge está um pouco grande, mas o que ela basicamente faz é olhar se existe um arquivo Makefile com timestamp mais recente e rodar um "make all" pra construir o executável. O acordo era o binário seria nomeado como "hacking" pra facilitar essa função de rodar. Ela então busca por hacking na chamada container_name = f"{team_name}_{challenge_nr}"
e verifica o timestamp. Se for mais novo, roda. Do contrário não faz nada. Ao rodar os programas é chamada a função dockerize( ), que nada mais é que uma chamada pra docker usando subprocess. O tempo de início e fim de execução são capturados em time_start e time_stop. O resutando é verificado com a função md5sum( ), que emula o funcionamento do programa md5sum em Linux.
Bom... já deu pra perceber que o que parecia simples foi ficando bem complicado. Vamos então dar uma olhada na função dockerize.
def dockerize(pwd, command, container_name=None):
#userid = os.getuid()
#groupid = os.getgid()
userid = 1000
groupid = 1000
docker_cmd = [ "docker",
"run" ]
if not container_name is None:
docker_cmd += [ f"--name={container_name}" ]
docker_cmd += [
"--rm",
f"--user={userid}:{groupid}",
"-w",
f"{pwd}",
"-v",
f"{pwd}:{pwd}",
"-v",
f"{ROOTDIR}:{ROOTDIR}",
"hackathon:latest" ]
cmd = docker_cmd + command.split()
print("Running:", " ".join(cmd))
return str(subprocess.check_output(cmd))
Nada muito sofisticado. Mas já deu pra ver que buscar o uid e gid deu problemas. Por quê? Porque eu deixei rodando numa instância de jenkins, que roda com seu próprio usuário. Tentei arrumar as permissões do diretório pro mesmo grupo, mas no fim foi mais fácil deixar o container rodar com uid e gid fixos. Se nunca fez isso em container, experimente. Funciona que é uma beleza:
docker run --rm --user=$(id -u):$(id -g) -v $PWD:$PWD -w $PWD ubuntu:18.04 ls -a
E isso seria tudo do programa. Tem a parte do update_results( ), mas vou comentar depois que é somente salvar os resultados no formato json.
O que deu errado? Muita coisa.
Alguns programas simplesmente travavam. Ficavam lá parados com algum crash de lib ou coisa do tipo.
Qual foi a solução. Bom... a solução foi usar threads. Mas como alguém já disse antes, você tem um problema de dead-lock e quando resolve corrigir usando threads você termina tendo 5 outros problemas. Mas foi o que fiz. O loop inicial então foi modificado pra isso aqui:
for directory in get_directories(HACKATHONREPOS):
full_path = f"{directory}/challenge-"
for i in range(1, 4):
if os.path.exists(f"{full_path}{i}"):
th = threading.Thread(target=challenge, args=(i, f"{full_path}{i}", timestamp)).start()
ths.append(th)
#challenge(i, f"{full_path}{i}", timestamp)
for th in ths:
th.join()
update_timestamp()
A diferença era que agora rodavam muita instâncias ao mesmo tempo, tudo em paralelo, e acabava usando CPU demais. Então pra resolver um problema, criei outro. Precisei criar um semáforo pra poder dizer quantos threads poderia rodar simultaneamentes. Lembram do update_results( ) que salvava em json? O que acontece quando várias threads tentam escrever no mesmo arquivo ao mesmo tempo? Ou você chega numa estado chamado race condition, ou simplesmente dados aparecem e somem. Então foi preciso criar dois semáforos: um pro número de threads e outro pra salvar o resultado.
O loop principal então ficou assim:
ths = []
q = threading.Semaphore(MAX_THREADS)
qs = threading.Semaphore(1)
for directory in get_directories(HACKATHONREPOS):
#print(directory)
full_path = f"{directory}/challenge-"
#print(full_path)
for i in range(1, 4):
if os.path.exists(f"{full_path}{i}"):
th = threading.Thread(target=challenge, args=(i, f"{full_path}{i}", timestamp, q, qs)).start()
ths.append(th)
#challenge(i, f"{full_path}{i}", timestamp, q, qs)
for th in ths:
th.join()
update_timestamp()
E já que dois novos parâmetros foram passados pra função challenge( ), como essa ficou internamente? Assim:
def challenge(chl_id, directory, timestamp, q, qs):
q.acquire()
print(chl_id, directory, timestamp)
os.chdir(directory)
challenge_nr = os.path.basename(os.path.realpath(directory))
team_name = os.path.basename(os.path.realpath(directory + "/.."))
print(directory, challenge_nr, team_name, os.path.realpath(os.path.curdir))
if os.path.exists(f"{directory}/Makefile"):
tmstp = get_mtime(f"{directory}/Makefile")
if tmstp > timestamp:
try:
dockerize(directory, "git clean -fdx")
except subprocess.CalledProcessError:
pass
try:
dockerize(directory, "make all")
except subprocess.CalledProcessError as e:
update_results({team_name: {challenge_nr: "failed: to build using make"} + str(e.output)}, qs)
print(team_name, challenge_nr, "done - failed to make: " + str(e.output))
q.release()
return
if not os.path.exists(f"{directory}/hacking"):
print(team_name, challenge_nr, "done - binary missing")
print(os.listdir("."))
q.release()
return
tmstp = get_mtime(f"{directory}/hacking")
if timestamp >= tmstp:
print(team_name, challenge_nr, "done - old timestamp")
q.release()
return
container_name = f"{team_name}_{challenge_nr}"
print("container_name:", container_name)
challenge_file = ROOTDIR + "/" + CHALLENGE_INPUTS[challenge_nr]
time_start = time.time()
try:
result = dockerize(directory, f"./hacking {challenge_file}", container_name)
except subprocess.CalledProcessError as e:
update_results({team_name: { challenge_nr : "failed: to run challenge: " + str(e.output)}}, qs)
print(team_name, challenge_nr, "done - failed to run: ", e.output)
q.release()
return
time_stop = time.time()
print(f"docker ended for {team_name} in {challenge_nr}")
md5 = md5sum(result)
print(f"md5 ended for {team_name} in {challenge_nr}")
if md5 == EXPECTED_RESULTS[challenge_nr]:
update_results({team_name: { challenge_nr : "failed: wrong md5 check"}}, qs)
else:
update_results({team_name: { challenge_nr : time_stop - time_start}}, qs)
print(team_name, challenge_nr, "done - arrived at the end")
q.release()
Basicamente um q.acquire( ) pra começar a rodar e um q.release( ) ao terminar. Tudo lindo. Vamos rodar? Via Jenkinks claro? E... problemas. Algumas dessas threads ficavam paradas. Caso não tenha reparado, ao final do loop principal existe esse pequeno trecho de código:
for th in ths:
th.join()
Ele basica diz o seguinte: pra cada thread nesse vetor de threads, espere a thread terminar. É isso que o join( ) faz. E como os programas travavam em sua execução o que acontecia? Dead-lock de novo.
O que fazer então? Vamos criar um sistema de monitoração chamado... soulkiller! Se jogou Cybepunk 2077 sabe do que estou falando, certo Silverhand? Então o soulkiller fica aguardando um certo tempo pra terminar a execução. Se passar daqui, uma vez sendo container basta simplesmente chamar um "docker kill <nome do container>". E aliás esse foi o motivo de eu passar o nome do container como argumento da função docker( ).
def soulkiller(container_name, timeout=None):
print("soulkiller has started for:", container_name)
time.sleep(3)
if timeout is None:
timeout = TIMEOUT
timeout -= 3
while timeout > 0:
resp = exec("docker ps -a")
if not re.search(container_name, resp):
print("soulkiller exiting since no container found for:", container_name)
return
timeout -= 1
time.sleep(1)
print("soulkiller reached timeout and will kill:", container_name)
exec(f"docker kill {container_name}")
E assim o sistema funcionou durante a hackathon. Era pra ser simples mas... bom... funcionou. Abaixo segue o script inteiro em todo sua beleza. Ou não.
#! /usr/bin/python3
import json
import os
import subprocess
import time
import hashlib
import threading
import queue
import re
MAX_THREADS = 1
TIMEOUT = 30 * 60 * 60
CHALLENGE_INPUTS = {
"challenge-1": "Employees-30M.json",
"challenge-2": "1GB.txt",
"challenge-3": "pi-1M.txt"
}
EXPECTED_RESULTS = {
"challenge-1": "d5c140cdc965be8ed56c35f570eaf83f",
"challenge-2": "2b4fd25f11d75c285ec69ecac420bd07",
"challenge-3": "731fa54d7133f61d4b3fac9b46bda927"
}
ROOTDIR = "/usr/local/tmp/hackathon"
TIMESTAMP = ROOTDIR + "/timestamp"
HACKATHONREPOS = ROOTDIR + "/repos"
RESULTS = ROOTDIR + "/results.json"
def get_directories(dir_name):
#print("dir_name:", dir_name)
directories = []
for filename in os.listdir(dir_name):
if filename[0] == ".":
#print(filename, " begins with a dot")
continue
filename = f"{dir_name}/{filename}"
if not os.path.isdir(filename):
#print(filename, " isn't a directory")
continue
#print("appending:", filename)
directories.append(filename)
return directories
def exec(command):
return str(subprocess.check_output(command.split()))
def dockerize(pwd, command, container_name=None):
#userid = os.getuid()
#groupid = os.getgid()
userid = 1000
groupid = 1000
docker_cmd = [ "docker",
"run" ]
if not container_name is None:
docker_cmd += [ f"--name={container_name}" ]
docker_cmd += [
"--rm",
f"--user={userid}:{groupid}",
"-w",
f"{pwd}",
"-v",
f"{pwd}:{pwd}",
"-v",
f"{ROOTDIR}:{ROOTDIR}",
"hackathon:latest" ]
cmd = docker_cmd + command.split()
print("Running:", " ".join(cmd))
return str(subprocess.check_output(cmd))
def update_timestamp():
with open(TIMESTAMP, 'w') as tmpstamp:
tmpstamp.write(str(time.time()))
def get_mtime(filename):
return os.stat(filename).st_mtime
def read_results():
j = json.loads("{}")
if os.path.exists(RESULTS):
#print(RESULTS, "exists")
with open(RESULTS) as results:
j = json.load(results)
return j
def save_results(j):
with open(RESULTS, "w") as output:
json.dump(j, output, indent=4)
def update_results(response_dict, qs):
print("Called update_result:", response_dict)
qs.acquire()
j = read_results()
print("Before:", j)
for team_name_resp in response_dict.keys():
for challenge_id_resp in response_dict[team_name_resp].keys():
value_resp = response_dict[team_name_resp][challenge_id_resp]
if not team_name_resp in j.keys():
j[team_name_resp] = { challenge_id_resp: value_resp }
elif not challenge_id_resp in j[team_name_resp].keys():
j[team_name_resp][challenge_id_resp] = value_resp
else:
if not challenge_id_resp in j[team_name_resp].keys():
j[team_name_resp][challenge_id_resp] = value_resp
else:
previous_value = j[team_name_resp][challenge_id_resp]
if isinstance(previous_value, float) and isinstance(value_resp, float):
if value_resp < previous_value:
j[team_name_resp][challenge_id_resp] = value_resp
else:
j[team_name_resp][challenge_id_resp] = value_resp
print("After:", j)
save_results(j)
qs.release()
def md5sum(message):
hash = hashlib.md5(message.encode())
return hash.digest()
def soulkiller(container_name, timeout=None):
print("soulkiller has started for:", container_name)
time.sleep(3)
if timeout is None:
timeout = TIMEOUT
timeout -= 3
while timeout > 0:
resp = exec("docker ps -a")
if not re.search(container_name, resp):
print("soulkiller exiting since no container found for:", container_name)
return
timeout -= 1
time.sleep(1)
print("soulkiller reached timeout and will kill:", container_name)
exec(f"docker kill {container_name}")
def challenge(chl_id, directory, timestamp, q, qs):
q.acquire()
print(chl_id, directory, timestamp)
os.chdir(directory)
challenge_nr = os.path.basename(os.path.realpath(directory))
team_name = os.path.basename(os.path.realpath(directory + "/.."))
print(directory, challenge_nr, team_name, os.path.realpath(os.path.curdir))
if os.path.exists(f"{directory}/Makefile"):
tmstp = get_mtime(f"{directory}/Makefile")
if tmstp > timestamp:
try:
dockerize(directory, "git clean -fdx")
except subprocess.CalledProcessError:
pass
try:
dockerize(directory, "make all")
except subprocess.CalledProcessError as e:
update_results({team_name: {challenge_nr: "failed: to build using make"}}, qs)
print(team_name, challenge_nr, "done - failed to make: " + str(e.output))
q.release()
return
if not os.path.exists(f"{directory}/hacking"):
print(team_name, challenge_nr, "done - binary missing")
print(os.listdir("."))
q.release()
return
tmstp = get_mtime(f"{directory}/hacking")
if timestamp >= tmstp:
print(team_name, challenge_nr, "done - old timestamp")
q.release()
return
container_name = f"{team_name}_{challenge_nr}"
print("container_name:", container_name)
threading.Thread(target=soulkiller, args=(container_name,), daemon=True).start()
challenge_file = ROOTDIR + "/" + CHALLENGE_INPUTS[challenge_nr]
time_start = time.time()
try:
result = dockerize(directory, f"./hacking {challenge_file}", container_name)
except subprocess.CalledProcessError as e:
update_results({team_name: { challenge_nr : "failed: to run challenge: " + str(e.output)}}, qs)
print(team_name, challenge_nr, "done - failed to run: ", e.output)
q.release()
return
time_stop = time.time()
print(f"docker ended for {team_name} in {challenge_nr}")
md5 = md5sum(result)
print(f"md5 ended for {team_name} in {challenge_nr}")
if md5 == EXPECTED_RESULTS[challenge_nr]:
update_results({team_name: { challenge_nr : "failed: wrong md5 check"}}, qs)
else:
update_results({team_name: { challenge_nr : time_stop - time_start}}, qs)
print(team_name, challenge_nr, "done - arrived at the end")
q.release()
if not os.path.exists(TIMESTAMP):
update_timestamp()
timestamp = get_mtime(TIMESTAMP)
ths = []
q = threading.Semaphore(MAX_THREADS)
qs = threading.Semaphore(1)
for directory in get_directories(HACKATHONREPOS):
full_path = f"{directory}/challenge-"
for i in range(1, 4):
if os.path.exists(f"{full_path}{i}"):
th = threading.Thread(target=challenge, args=(i, f"{full_path}{i}", timestamp, q, qs)).start()
ths.append(th)
#challenge(i, f"{full_path}{i}", timestamp, q, qs)
for th in ths:
try:
th.join()
except:
pass
update_timestamp()
Se estiver lendo com atenção notará com que rodei com... 1 thread só. Como eram desafios que exigiam computação e o parâmetro era tempo pra decidir o vencedor, no fim eu decidi deixar uma thread só pra ser justo.
Desde que escrevi o artigo trabalhando de home-office para descrever como era meu ambiente de trabalho em casa eu fiz alguns updates e upgrades nas configurações. O primeiro e mais notável é que dei uma boa arrumada na estante à direita. E acreditem que não foi fácil. Mas ao menos agora durante as conferências e gravações do canal Unix Load On fica um fundo de tela decente.
Então vamos ao updates mais visualmente claros, começando pela cadeira.
A diferença básica dessa cadeira pra anterior é que ela mantém as costas retas e tem a almofada pra lombar. Isso ajuda pra quem passa muito tempo sentado, que é o meu caso. E, claro, comprei uma cadeira que aguenta até 125 Kg pra aguentar o peso extra adquirido durante essa pandemia.
Também fiz um upgrade nos hubs USB.
Agora com somente um único hub USB3 de 7 portas. Genérico da China. Pra trocar entre meu desktop pessoal e o laptop corporativo agora é somente mudar um cabo.
Outra melhoria, aparente mas subjetiva foi a mesa.
O sindicato negociou com a firma a aquisição de uma luminária e uma mesa com altura regulável. Achei o máximo, principalmente pra poder trabalhar em pé e dar um alívio pras costas. Mas essa não é exatamente a mesa que foi comprada. Como a tampa de madeira era menor eu meio que adaptei na mesa que eu tinha, que é do Ikea, e só coloquei as pernas reguláveis. Ficou até que bom. A luminária está entre os monitores. Infelizmente ficam esses montes de cabos pendurados... mas tá de bom tamanho. E por causa da mudança de altura eu precisei mudar o desktop pra cima da mesa. Ficou mais congestionado, mas ainda sobra espaço pra trabalhar.
Quem também sofreu upgrade foram tanto o braço mecânico quanto o microfone que uso pra gravar os webcasts do canal Unix Load On.
O de antes, que ainda está lá atrás desse e sem uso, ficava na frente da tela. Não tinha forma de ajustar que não atrapalhasse a visão. Já o microfone eu resolvi dar um upgrade na esperança de ter uma captação de voz melhor, não que isso conseguisse esconder minha voz afônica. Mas foi uma decepção. Não trouxe nenhum grande benefício e ainda é do tipo mono. Podia ter gastado esse dinheiro em cerveja. Comprei também a espuma pra minizar problemas de captura mas não posso dizer que tenha feito grande diferença. Mais uma cerveja perdida.
Já a câmera web, que ainda é a mesma Logitech c920, ganhou um braço mecânico pra sustentar próximo do monitor.
Privacy Shutter Lens Cap Hood Cover for Logitech HD Pro Webcam C920 C922 C930e
Já aproveitei e também mandei um filtro de privacidade na câmera. Mesmo só usando Linux, a gente nunca sabe...
Por fim o último, um mimo que recebi. Como o festival de rock Sweden Rock foi cancelado ano passado e também esse ano, eu tenho comprado parte do merchandise deles pra ajudar a manter o festival vivo. Na última compra eles enviaram esse copo de café ou chá que no fim eu adorei. Como ele é todo vedado não tem risco de derramar no teclado, o que já fiz algumas vezes. Tive sorte de que os copos estavam quase vazios.
Ao contrário de tudo que postei aqui, esse copo vedado eu não tenho link. Mesmo porque não comprei e veio de brinde. Mas deixo aqui o link com os acessórios disponíveis na loja do Sweden Rock (eu já comprei quase todos os ítens que estão lá).
https://merch.swedenrock.com/merch/accessoarer
E pra fechar eu vou deixar um foto no mesmo estilo do artigo anterior. Com camiseta de pijama de alguma empresa.
Nota: esse foi um artigo que escrevi pra Revista Espírito Livre, mas a edição nunca saiu. Mantive no mesmo formato e estilo que sairia na revista.
Algumas pessoas podem não acreditar ou até mesmo pensar que é loucura mas o software livre trata sobre... software! Nesse momento imagino já algumas pessoas revoltadas jogando cadeiras e ameaçando colocar fogo nas lixeiras mas infelizmente o software livre é sobre software. O software livre nos dá as diretivas pra termos um software que seja livre, mas não diz nada sobre como escrever esse software. Nenhuma direção, nada. Apenas sobre sua licença.
E software, apesar de ser uma forma de comunicação com um computador que interpretará as diretivas como uma linguagem de máquina e mandará sua CPU executar os passos descritos, pode e deve ser legível por seres humanos. Sim! Software é uma forma de escrita e como tal é imperativo para que seja lido e entendido tanto por CPUs quanto por humanos.
Existe então um movimento chamado "software craftmanship". A tradução remonta aos forjadores de armadura da idade média (não que o termo venha dessa época, é apenas a referencia feita). Uma tradução mais próxima seria algo como "forjadores de programação". Esse movimento tem seu manifesto explicitado em:
https://manifesto.softwarecraftsmanship.org/#/pt-br
Descrevendo o mesmo literalmente aqui:
Como aspirantes a verdadeiros Artesãos de Software, estamos ampliando as fronteiras da qualidade no desenvolvimento profissional de programas de computadores através da prática e ensinando outros a aprender nossa arte. Graças a esse trabalho, valorizamos:
Não apenas software em funcionamento,
mas software de excelente qualidade
Não apenas responder a mudanças,
mas agregar valor de forma constante e crescente
Não apenas indivíduos e suas interações,
mas uma comunidade de profissionais
Não apenas a colaboração do cliente,
mas parcerias produtivas.
Sendo assim descobrimos, que para atingir os objetivos à esquerda, os que estão à direita são indispensáveis.
A tradução existente no site usa o termo "artesãos de software" mas eu prefiro como "forjadores de software". Preciosidades linguísticas à parte, é possível ver que o foco é que todo código seja lido e interpretado por pessoas.
Um dos pais desse movimento é Robert C. Martin, ou como é mais conhecido, uncle Bob. Além de ter escrito os livros que são referências para clean code, Clean Code: A handbook of Agile Software Craftsmanship[1] e The Clean Coder: A Code of Conduct for Professional Porgrammers[2], ele também tem uma série de vídeos a respeito que ajudam a entender e aplicar os conceitos de clean code. O primeiro episódio[3] está disponível gratuitamente.
Clean code é uma prática ativa de software e um artigo somente não é o suficiente pra cobrir todo o assunto. Então descreverei apenas uma pequena parte referente sobre como escrever código de uma forma prática. Usarei shell script como exemplo por ser uma das primeiras linguagens de programação pra quem inicia no mundo Unix e ter uma curva de aprendizado mais baixa se comparada com linguagens como C.
Em clean code a ideia é escrever o código próximo da linguagem falada. Então algumas dicas são usar variáveis com nome significativo e lembrando substantivos. Então um loop for como esse:
for f in *txt
do
...
done
A variável "f" é claramente um padrão não desejado. O que significa "f"? De acordo com os fundamentos o melhor seria:
for fileName in *txt
do
...
done
Eu escolhi manter o padrão em inglês, mas claro que ali poderia ser "nomeArquivo". Eu usei a notação chamada "camelCase". Poderia ter usado for formato "file_name", snake case, mas esse tipo de detalhismo não é relevante pra clean code. O melhor seria seguir o padrão que o time usa no código em geral. No meu time trabalhamos muito com a linguagem go, que usa a forma "camelCase" e por isso acabo adotando no meu código.
Outra regra pras variáveis é tentar decidir se o nome é longo ou curto baseado em seu escopo de uso. Se a variável é muito usado, o melhor é ter um nome longo e bem descritivo. Se seu uso é curto, seu nome também pode ser curto. Então se dentro de um programa existir uma pequena função que apenas faça um sleep:
for i in $(seq 1 10)
do
echo $i
done
Esse não é lá um código muito útil, mas ilustra que pra um pequeno loop uma variável "i" pode ser aceita. Sempre lembando que depende do escopo de uso da variável.
Já as funções em clean code seguem a recomendação inversa. Se for muito usada no escopo do programa, o melhor é usar um nome mais curto. Se for pouco usada, um nome mais longo. E sempre usar um verbo pra funções. Ampliando o código do exemplo anterior, é possível criar uma função que retorne os nomes de todos os arquivos passados em seu argumento como padrão, algo como "*.txt". Como o exemplo é em shell script, não é possível usar return, mas um "echo" diretamente pra ler a resposta.
O código então poderia ser:
getFileNames() {
pattern=$1
for fileName in *$pattern
do
echo $fileName
done
}
allTXTFiles=$(getFileNames txt)
Então a função vira um "pegar os nomes de arquivos". O substantivo "allTXTFiles" tem o verbo "pegar os nomes de arquivos txt", ou ficando mais próximo da língua falada: "todos os arquivos TXT" é "pegar os nomes dos arquivos txt".
Booleanos são variáveis com valores sim ou não, verdadeiro ou falso. Seu uso em clean code pode ser traduzido como pergunta. Então parâmetros booleanos podem ser valores como "temperatura é alta", "isTemperatureHigh".
Um pequeno exemplo com um script pra verificar se é fim de semana.
getWeekend() {
weekDay=$(date +%a)
echo $weekDay | egrep -q "Sun|Sat"
echo $?
}
isWeekend=$(getWeekend)
if [ $isWeekend ]; then
echo "This is weekend."
else
echo "This is a working day."
fi
Os comentários em código significam que ou seu código está tão mal escrito que precisa ter explicação. Então em clean code deve-se evitar o uso de comentários, mas se forem necessários, que sejam extremamente descritivos.
Então um exemplo de comentário pro meu código acima.
# it returns 0 for Sundays or Saturdays, 1 for every other day.
getWeekend() {
weekDay=$(date +%a)
echo $weekDay | egrep -q "Sun|Sat"
echo $?
}
Meu comentário não descreve a função, que o nome já faz isso. Apenas melhora o entendimento do que voltará como parâmetro.
Eu pedi um exemplo pra aplicar os princípios de clean code às pessoas do grupo de shell script no telegram[4]. Agradeço então ao usuário @fonini por ter fornecido seu código. O código em questão é esse (apenas uma parte do código):
ts="$(date +%Y%m%d.%H%M%S.%N)" # timestamp
if [[ ! -v DEBUGFILE ]]; then
printf "Set DEBUGFILE first.\n" 1>&2
exit 1
fi
if (( $# < 1 )); then
printf '%s\n%s\n' \
"usage: DEBUGFILE=filename debug format [args]" \
" e.g.: export DEBUGFILE=log.txt"
exit 2
fi
O que pode ser melhorado nesse código? Segue o que seria uma aplicação dos princípios de clean code no sell script.
checkForDebug(){
if [[ ! -v DEBUGFILE ]]; then
printf "Set DEBUGFILE first.\n" 1>&2
exit 1
fi
}
printUsage() {
printf '%s\n%s\n' \
"usage: DEBUGFILE=filename debug format [args]" \
" e.g.: export DEBUGFILE=log.txt"
}
validateNumberArguments() {
totalArguments=$1
if (( $totalArguments < 1 )); then
printUsage
exit 2
fi
}
timestamp="$(date +%Y%m%d.%H%M%S.%N)"
checkForDebug
numberOfArguments=$#
validateNumberArguments $numberOfArguments
Não mentirei que clean code é fácil. É uma disciplina: é preciso praticar o tempo todo pra conseguir aplicar. No começo não é fácil, mas com o tempo seu código vai melhorando a legibilidade. E isso é apenas uma pequena parte de clean code. Tentarei descrever mais nas próximas edições.
Sobre o autor:
|
Referências:
[1] Clean Code: A Handbook of Agile Software Craftsmanship - http://hl.eng.br/6cX
[2] The Clean Coder: A Code of Conduct for Professional Programmers - http://hl.eng.br/6cW
[3] Clean Code: Fundamentals, Episode 1 - https://cleancoders.com/video-details/clean-code-episode-1
[4] Shell Script (#!/bin/bash) - https://t.me/shellbr
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.
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:
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).
Toda vez que gravamos o Unix Load On (Canal Unix Load On), eu acabo fazendo uma seleção tosca do que vamos falar. Isso sempre me deu uma coceira de resolver. E nada melhor que o bom e velho Python.
Hoje eu coloquei as mangas de fora e fiz funcionar. No bom e velho modo script: rodo num shell, e pego o resultado. Mas cheguei no ponto em que gostaria de que isso estivesse disponível em modo web, pra eu poder mostrar durante o programa diretamente no browser.
O Python fornece o módulo SimpleHTTPServer, que aliás parece que no Python3 virou uma classe de http.serve. Mas tem. Ele mostra o filesystem via interface http a partir de onde você chama o módulo. Não deveria ser complicado fazer o output do meu script ir pra uma interface http. E realmente foi o que fiz.
def start():
class Handler(BaseHTTPRequestHandler):
def do_GET(self):
self.send_response(200)
self.send_header("Content-type", "text/html")
self.end_headers()
client_ip, client_port = self.client_address
reqpath = self.path.rstrip()
print(f"request from {client_ip}:{client_port} for {reqpath}")
article = get_final_article()
title = get_title(article)
link = get_link(article)
response = f"""
<h1>Title: <a href="/{link}">{title}
<h2>Link: <a href="/{link}">{link}</a></h2>
"""
content = bytes(response.encode("utf-8"))
self.wfile.write(content)
Se leu com atenção vai ver que a linha do artigo eu pego em "article = get_final_article()". Isso retorna algo como:
* [Linux developers get ready to wield the secateurs against elderly microprocessors • The Register](https://www.theregister.com/2021/01/11/linux_olld_cpus/)
Dai o restante é sanitizar cada um pra mostrar corretamente.
O script todo pode ser visto aqui: https://github.com/helioloureiro/homemadescripts/blob/master/random_article.py
O resultado no console é algo parecido com isso aqui:
./random_article.py
127.0.0.1 - - [26/Mar/2021 21:31:27] "GET /newarticle? HTTP/1.1" 200 -
request from 127.0.0.1:46060 for /newarticle?
= Articles =
* /helioloureiro/canalunixloadon/blob/master/pautas/2016030.md
* /helioloureiro/canalunixloadon/blob/master/pautas/2016040.md
* /helioloureiro/canalunixloadon/blob/master/pautas/2016041.md
* /helioloureiro/canalunixloadon/blob/master/pautas/2016050.md
* /helioloureiro/canalunixloadon/blob/master/pautas/2016051.md
* /helioloureiro/canalunixloadon/blob/master/pautas/2016090.md
* /helioloureiro/canalunixloadon/blob/master/pautas/2016100.md
* /helioloureiro/canalunixloadon/blob/master/pautas/2016110.md
* /helioloureiro/canalunixloadon/blob/master/pautas/2016120.md
* /helioloureiro/canalunixloadon/blob/master/pautas/2017010.md
* /helioloureiro/canalunixloadon/blob/master/pautas/2017020.md
* /helioloureiro/canalunixloadon/blob/master/pautas/2017030.md
* /helioloureiro/canalunixloadon/blob/master/pautas/2017040.md
* /helioloureiro/canalunixloadon/blob/master/pautas/2017060.md
* /helioloureiro/canalunixloadon/blob/master/pautas/2017061.md
* /helioloureiro/canalunixloadon/blob/master/pautas/2017070.md
* /helioloureiro/canalunixloadon/blob/master/pautas/2017080.md
* /helioloureiro/canalunixloadon/blob/master/pautas/2017110.md
* /helioloureiro/canalunixloadon/blob/master/pautas/2018030.md
* /helioloureiro/canalunixloadon/blob/master/pautas/2018050.md
* /helioloureiro/canalunixloadon/blob/master/pautas/2018060.md
* /helioloureiro/canalunixloadon/blob/master/pautas/2018080.md
* /helioloureiro/canalunixloadon/blob/master/pautas/2018100.md
* /helioloureiro/canalunixloadon/blob/master/pautas/2019020.md
* /helioloureiro/canalunixloadon/blob/master/pautas/2019040.md
* /helioloureiro/canalunixloadon/blob/master/pautas/2019070.md
* /helioloureiro/canalunixloadon/blob/master/pautas/2019110.md
* /helioloureiro/canalunixloadon/blob/master/pautas/20200717.md
* /helioloureiro/canalunixloadon/blob/master/pautas/20200807.md
* /helioloureiro/canalunixloadon/blob/master/pautas/20201001.md
* /helioloureiro/canalunixloadon/blob/master/pautas/20201015.md
* /helioloureiro/canalunixloadon/blob/master/pautas/20201029.md
* /helioloureiro/canalunixloadon/blob/master/pautas/20201115.md
* /helioloureiro/canalunixloadon/blob/master/pautas/20201204.md
* /helioloureiro/canalunixloadon/blob/master/pautas/20210121.md
* /helioloureiro/canalunixloadon/blob/master/pautas/20210205.md
* /helioloureiro/canalunixloadon/blob/master/pautas/20210215.md
* /helioloureiro/canalunixloadon/blob/master/pautas/20210312.md
* /helioloureiro/canalunixloadon/blob/master/pautas/20210325.md
* /helioloureiro/canalunixloadon/blob/master/pautas/20210410.md
Latest: /helioloureiro/canalunixloadon/blob/master/pautas/20210410.md
https://raw.githubusercontent.com/helioloureiro/canalunixloadon/blob/master/pautas/20210410.md
Article selected: * [Linux developers get ready to wield the secateurs against elderly microprocessors • The Register](https://www.theregister.com/2021/01/11/linux_olld_cpus/)
title: Linux developers get ready to wield the secateurs against elderly microprocessors • The Register
article: * [Linux developers get ready to wield the secateurs against elderly microprocessors • The Register](https://www.theregister.com/2021/01/11/linux_olld_cpus/)
Já faz anos que deixei de usar FreeBSD como principal systema operacional, inclusive no meu laptop. Um dos motivos que me levaram a isso na época foi o fato da hibernação não funcionar corretamente (naquela época). O post que fiz da migração foi esse aqui: Migrando para OpenSuse. Como mudei pra Suécia pra virar um desenvolvedor de software em Linux, não fazia muito sentido continuar ou tentar voltar ao FreeBSD como sistema principal.
Mas sempre tentei manter um pé por ali. FreeBSD é um sistema fantástico pra ser usado. E com as facilidades de CPUs com múltiplos núcleos e avanço no sistemas de virtualização, ficou fácil ter sempre à mão uma VMzinha com o diabinho rodando.
Meu eu sempre criava a VM, usava por um tempo, e depois removia. Simplesmente parei de fazer upgrades. Só testava algumas features e ficava por isso mesmo.
Após uma explicação muito boa do sistema de upgrades feita pelo Garga (@rbgarga) no grupo de FreeBSD (FUG-BR) no Telegram (https://t.me/fug_br), eu resolvi experimentar.
A explicação:
Existem 3 casos de uso do freebsd-update:
1. Atualizar de X.Y pra X.Y-p1
2. Atualizar de X.Y pra X.Y+1
3. Atualizar de X.Y pra X+1.Y
No caso 1, apenas correções de segurança, então vc vai rodar freebsd-update fetch install, reboot e já era.
No caso 2, como vc tá atualizando pra uma nova versão de kernel, dentro da mesma série, vc vai fazer freebsd-update -r X.Y+1 upgrade pra ele baixar tudo o que precisa, aí vai fazer freebsd-update install pra ele instalar o kernel novo, então reboot no kernel novo, confere que tá tudo em ordem e freebsd-update install novamente pra ele atualizar o world, aí vc reinicia ( ou faz um reboot se for possível ) e pronto.
No caso 3 você tá trocando de major version, do 12 pro 13 por exemplo, então vc repete o processo do caso 2, porém, depois do segundo freebsd-update install vc vai estar em um sistema rodando world e kernel do 13, porém com pacotes ainda do 12. Então vc atualiza todos os pacotes e depois roda o último freebsd-update install pra ele remover todos os arquivos antigos do 12 que não são mais usados no 13 (equivalente ao make delete-old delete-old-libs). Se rodar isso antes de reinstalar todos os pacotes, com certeza vai quebrar coisas
Então segui as direções num upgrade do 12.2-RELEASE pro 13.0-RC1, o caso 3.
root@freebsd:~ # freebsd-update -r 13.0-RC1 upgrade
src component not installed, skipped
Looking up update.FreeBSD.org mirrors... 3 mirrors found.
Fetching metadata signature for 12.2-RELEASE from update4.freebsd.org... done.
Fetching metadata index... done.
Inspecting system... done.
The following components of FreeBSD seem to be installed:
kernel/generic kernel/generic-dbg world/base world/base-dbg world/doc
world/lib32 world/lib32-dbg
The following components of FreeBSD do not seem to be installed:
Does this look reasonable (y/n)? y
Fetching metadata signature for 13.0-RC1 from update4.freebsd.org... done.
Fetching metadata index... done.
Fetching 1 metadata patches. done.
Applying metadata patches... done.
Fetching 1 metadata files... done.
Inspecting system... done.
Fetching files from 12.2-RELEASE for merging... done.
Preparing to download files... done.
Fetching 10251 patches.....10....20....30....40....
[editado]
....10230....10240....10250 done.
Applying patches...
Fetching 638 files... ....10....20....
[editado]
...620....630.... done.
Attempting to automatically merge changes in files... done.
The following file will be removed, as it no longer exists in
FreeBSD 13.0-RC1: /etc/motd
Does this look reasonable (y/n)? y
The following files are affected by updates. No changes have
been downloaded, however, because the files have been modified
locally:
/var/db/etcupdate/log
The following files will be removed as part of updating to
13.0-RC1-p0:
/boot/boot1.efifat
13.0-RC1-p0:
/boot/boot1.efifat
/boot/gptboot.efifat
/boot/kernel/aha.ko
[...]
The following files will be added as part of updating to
13.0-RC1-p0:
/boot/fonts
/boot/fonts/10x18.fnt.gz
/boot/fonts/10x20.fnt.gz
/boot/fonts/11x22.fnt.gz
[...]
The following files will be updated as part of updating to
13.0-RC1-p0:
/.cshrc
/.profile
/COPYRIGHT
/bin/[
/bin/cat
/bin/chflags
[...]
To install the downloaded upgrades, run "/usr/sbin/freebsd-update install".
root@freebsd:~ # /usr/sbin/freebsd-update install
src component not installed, skipped
Installing updates...
Kernel updates have been installed. Please reboot and run
"/usr/sbin/freebsd-update install" again to finish installing updates.
root@freebsd:~ # reboot
Mar 10 11:39:50 freebsd reboot[65100]: rebooted by root
Mar 10 11:39:50 freebsd syslogd: exiting on signal 15
[...]
root@freebsd:~ # uname -a
FreeBSD freebsd 13.0-RC1 FreeBSD 13.0-RC1 #0 releng/13.0-n244639-60e8939aa85: Fri Mar 5 05:01:01 UTC 2021 This email address is being protected from spambots. You need JavaScript enabled to view it. :/usr/obj/usr/src/amd64.amd64/sys/GENERIC amd
64
root@freebsd:~ # /usr/sbin/freebsd-update install
src component not installed, skipped
Installing updates...
Scanning //usr/share/certs/blacklisted for certificates...
Scanning //usr/share/certs/trusted for certificates...
Completing this upgrade requires removing old shared object files.
Please rebuild all installed 3rd party software (e.g., programs
/bin/[
/bin/cat
/bin/chflags
[...]
To install the downloaded upgrades, run "/usr/sbin/freebsd-update install".
root@freebsd:~ # /usr/sbin/freebsd-update install
src component not installed, skipped
Installing updates...
Kernel updates have been installed. Please reboot and run
"/usr/sbin/freebsd-update install" again to finish installing updates.
root@freebsd:~ # reboot
Mar 10 11:39:50 freebsd reboot[65100]: rebooted by root
Mar 10 11:39:50 freebsd syslogd: exiting on signal 15
[...]
root@freebsd:~ # uname -a
FreeBSD freebsd 13.0-RC1 FreeBSD 13.0-RC1 #0 releng/13.0-n244639-60e8939aa85: Fri Mar 5 05:01:01 UTC 2021 This email address is being protected from spambots. You need JavaScript enabled to view it. :/usr/obj/usr/src/amd64.amd64/sys/GENERIC amd
64
root@freebsd:~ # /usr/sbin/freebsd-update install
src component not installed, skipped
Installing updates...
Scanning //usr/share/certs/blacklisted for certificates...
Scanning //usr/share/certs/trusted for certificates...
Completing this upgrade requires removing old shared object files.
Please rebuild all installed 3rd party software (e.g., programs
installed from the ports tree) and then run "/usr/sbin/freebsd-update install"
again to finish installing updates.
root@freebsd:~ # reboot
Mar 11 17:25:34 freebsd reboot[43513]: rebooted by root
Mar 11 17:25:34 freebsd syslogd: exiting on signal 15
[...]
root@freebsd:~ # file /usr/local/bin/htop
/usr/local/bin/htop: ELF 64-bit LSB executable, x86-64, version 1 (FreeBSD), dynamically linked, interpreter /libexec/ld-elf.so.1, for FreeBSD 12.0 (1200086), FreeBSD-style, stripped
root@freebsd:/usr/ports/ports-mgmt/pkg # pkg update -f
Updating FreeBSD repository catalogue...
Fetching meta.conf: 100% 163 B 0.2kB/s 00:01
Fetching packagesite.txz: 100% 6 MiB 6.4MB/s 00:01
pkg: No trusted certificates
Unable to update repository FreeBSD
Error updating repositories!
E fiquei travado nesse problema de "no trusted certificates" pra atualizar os pacotes.
Levei um tempo buscando solução, que indicavam pegar os certificados do /usr/src, que eu não instalo mais, mas achei a informação de ouro que corrigiu o problema. A instalação sobrescreveu a configuração em /etc/pkg/FreeBSD.conf e isso causou o erro. Bastou trocar o parâmetro "signature_type" para "none" que consegui atualizar os pacotes pra binários do FreeBSD-13.0-RC1.
root@freebsd:~ # cat /etc/pkg/FreeBSD.conf
# $FreeBSD$
# echo "FreeBSD: { enabled: no }" > /usr/local/etc/pkg/repos/FreeBSD.conf
#
FreeBSD: {
url: "pkg+http://pkg.FreeBSD.org/${ABI}/quarterly",
mirror_type: "srv",
signature_type: "none",
fingerprints: "/usr/share/keys/pkg",
enabled: yes
}
Sobre o FreeBSD-13.0? Não muito a dizer até onde mexi, mas tem novas features interessantes que espero testar mais a fundo. No momento estou deixando o ambiente preparado pra talvez fazer uma palestra dobre Python no FreeBSD.
Com o spoiler to título, já sabem que não tive COVID. Mas fiquei doente, bastante, e vou contar como funcionou o sistema de saúde sueco.
Tudo começou numa segunda-feira (tinha de ser coisa de segunda-feira mesmo), ao fim do dia. Comecei a sentir aquela típica dor nas costas e calafrios de febre. Não deu outra: 39°C de febre dali uma meia hora. Como eu já tinha tido uma virose um tempo atrás, já sabia que o procedimento era aguardar pelos menos 2 dias antes de entrar em contato com o sistema de saúde.
Passado os 2 dias, nada da febre baixar, então procurei o sistema de saúde. Se é uma emergência, o número é 112. Do contrário, o número é o 1177. É possível acessar o sistema de saúde por web também, através do https://1177.se, mas eu preferi por telefone. Ao ser atendido, uma das opções é pra continuar em inglês. Sei que existem suportes a outras línguas como árabe e eritréia (na verdade não sei como chamam a língua da Eritréia em português, que fica Eritrean em inglês). Fiquei uns 2 minutos na espera até que alguém atendeu a ligação. Conversamos sobre os sintomas e a pessoa disse que realmente poderia ser COVID. Então eu teria de estar isolado em casa, o que já tinha feito desde o primeiro dia, e pedir o teste pra confirmar. Pra eu não ter de decifrar como pedir o teste, foi enviado um SMS com o link direto pra eu fazer o pedido. E fiz.
Eu tinha ligado no terceiro dia de manhã, mas o teste só foi entregue no dia seguinte, pois a fila de gente pedindo era grande. Foi entregue em casa por um motorista de táxi. Ele me telefonou antes de chegar avisando que estava a caminho.
O kit de teste era esse aqui:
Nada de muito sofisticado. Um cotone, um prato descartável, uma cápsula pra conter o cotonete e saquinhos plásticos pra fechar tudo. Minhas informações estava no QR-code borrado. Veio tudo em sueco mas...
instruções em inglês no site.
Fiz a coleta, ligue de novo pro motorista e deixei a amostra do lado de fora da porta.
Nesse meio tempo segui apenas a recomendação que passaram: tomar algum remédio pra baixar a febre e não ficar o tempo todo na cama. Doses de paracetamol deram conta do recado.
No mesmo dia em que fiz o exame eu já recebi o resultado. Era umas 10 ou 11 horas da noite, mas veio por SMS. Teste negativo.
Foi um grande alívio, mas mesmo assim passei 5 dias com febre. Liguei no 1177 no dia seguinte e eles disseram que poderia mesmo ser uma gripe. Mas em caso da febre continuar persistindo eu deveria buscar o posto de saúde próximo de casa pra buscar ajuda. Não precisei.
Não houve nenhuma recomendação ou sugestão pra usar coisas como cloroquina ou ivermectina, remédios que fazem a cabeça dos brasileiros. Apenas algo pra manter a febre baixa. E beber bastante água.
Talvez no próximo post eu explique um pouco como funciona o sistema de postos de saúde aqui.
Esse é um artigo meio que requentado. Eu o preparei no ano passado pra uma edição da Revista do Espírito Livre sobre a pandemia que no fim nunca foi lançada. O que descrevo é um retrato de como estava a situação da Suécia na época. Estamos em 2021 e muita coisa mudou. E pra pior. Mas isso eu deixo pra comentar mais pra frente em outro artigo.
---
Não acompanho muito de perto como está a pandemia no Brasil. Mas vejo pelas notícias que chegam por aqui que está uma bagunça geral e que em certos pontos citam a Suécia como referência. E não só no Brasil: nos EUA também vejo muitas pessoas citando a Suécia como exemplo, no início como bom, e ultimamente como mau.
Eu já vivo no coração da escandinávia, em Estocolmo na Suécia, mais de 5 anos e vou descrever um pouco de como foi e está sendo enfrentada a pandemia por aqui, como as coisas mudaram, como tudo foi afetado. Vou aproveitar o parágrafo pra pedir desculpas pelos ângulos das fotos. Todas foram tiradas de uma câmera gopro que uso em meu capacete quando estou andando de bicicleta. E como é um bicicleta road, o ângulo sai um pouco pra baixo.
A Suécia não decide sua política pública de saúde por meio de ministros ou governo. Existe uma autoridade de saúde que publica os fundamentos das políticas a serem adotadas e todos seguem. O governo poderia ir contra o que é recomendado mas essa autoridade é composta por epidemiologistas especialistas. A cara do órgão é Anders Tegnell, epidemiologista reputadíssimo que chefiou inclusive equipes na luta contra ebola. É ele quem geralmente aparece no relatório diário que é feito sobre a evolução da epidemia. Não somente ele, mas a equipe que trabalha lá decide quais são as medidas adotadas por aqui.
Mas nem tudo são flores. Muitos outros cientistas, também renomados, são contra as medidas adotadas aqui. E chegaram mesmo a fazer uma carta pública contra Tegnell. Mas aparentemente aqui a ciência fala mais alto e ele continua sendo o chefe por lá.
Entre as decisões tomadas pra conter o avanço da doença, foram decretadas as seguintes medidas: distanciamento social, creches e escolas primárias abertas, restaurantes e bares só atendem clientes sentados em mesas e existem espaçamentos de 1 mesa entre cada mesa, que trabalhem de casa os que puderem, completo isolamento de asilos, e permitidos agrupamentos com menos de 50 pessoas.
Essas medidas parecem simples e fogem do padrão de lockdown que muitos outros fizeram como solução, como foram os casos da Espanha e da Itália. O argumento aqui foi que não existe nenhum estudo comprovando a eficácia do isolamento, que uma vez tendo as pessoas novamente em circulação, o vírus espalhará como faria normalmente e que as pessoas não são estúpidas. Todos entendem que há uma pandemia lá fora.
Isso resultou em ruas vazias. Muitos pequenos negócios estão falindo, inclusive restaurantes e bares que podem ficar abertos. As ruas costumam ficar desertas. O empreendedor que dirigia táxi ou uber está praticamente sem trabalho. Serviços como cabeleireiros estão vazios. Lojas estão às moscas. Não declarar lockdown não foi o que salvou o comércio aqui. Pra amenizar os efeitos econômicos da pandemia, o governo editou uma série de pacotes de ajuda, como pagar 40% do salário de quem teve o número de horas trabalhadas reduzidas, sendo a empresa arcando com 50% e os 10% restantes seriam por conta do trabalhador. E até mesmo o pagamento de financiamento de casa pode ser suspenso por mais de 1 ano.
Mesma rua, um pouco à frente da foto anterior. Algumas pessoas nas mesas de fora, o que dá a sensação de estar cheio, mas são apenas essas pessoas. Dentro o bar estava vazio.
Como não estamos em lockdown o governo recomenda que todos saiam de casa pra fazer exercícios e aproveitar o sol, que é uma coisa rara na Suécia e costuma sumir por longos 4 meses de pesado inverno. O ponto que levam em questão é que para saúde mental é importante as pessoas manterem hábitos saudáveis como andar pelos parques, andar de bicicleta ou correr. E muita gente segue essa recomendação por aqui, inclusive eu.
E quem olhar bem atentamente pras fotos vai perceber que não há quase ninguém usando máscaras. Esse é outro ponto controverso, mas a autoridade de saúde diz que não há comprovação da eficácia do uso de máscaras contra o COVID-19. Por favor não enviem mails ou mensagens me xingando ou links pra artigos que dizem o contrário. Eu não sou a autoridade sueca, que conta com médicos especialistas em doenças infecciosas. Mas em geral é o que acontece quando comento esse item.
Qual o resultado pra essa política? Até agora o número de mortos é muito acima dos outros países nórdicos, mas todos implementaram lockdown. Esse é o grande drama do modelo sueco: as pessoas não estão enclausuradas em casa, mas isso fez com que o número de mortos fosse grande. Um dos maiores do mundo (estamos chegando ao número de 5 mil mortos com o número de mortos por dia caindo). O argumento é que não faltaram leitos em UTI pra essas pessoas, o que é verdade. Houve preparo com até hospital de campanha sendo criado em 2 semanas, e que foi desmontado recentemente por falta de uso. No pico da contaminação da doença o número de leitos utilizados foi por volta de 500 dos 1500 disponíveis. Atualmente esse número é abaixo de 400. A taxa de mortalidade do vírus é muito alta em idosos, que foi onde a Suécia admite que errou: deveria ter bloqueado acesso aos idosos desde o início. E treinado melhor os funcionários de asilos, pois há relatos de que muitos foram trabalhar sem equipamentos de proteção adequados e mesmo alguns com sintomas de gripe.
Outro ponto que as estatísticas mostraram foi que os mais afetados em sua maioria eram idosos de famílias de imigrantes. A teoria é de que ao contrário dos suecos, existem pessoas de diferentes idades vivendo na mesma residência e isso permitiu o espalhamento da doença mais facilmente entre esses avôs e avós que viviam com os netos na mesma residência. Nesse ponto traçam um certo paralelo com o que aconteceu na Itália, onde dizem ser algo parecido em termos de moradia.
Ao contrário dessas famílias, o povo nórdico gosta de viver sozinho. O índice de pessoas que vivem só é algo em torno de 56% da população. Bom… não posso nem reclamar pois sou praticamente parte dessa estatística.
Escrevi bastante sobre o corona vírus e como a sociedade nórdica tem enfrentado o mesmo, mas não escrevi nada sobre software livre. Dos eventos que eu em geral participava, todos viraram online ou serão online. Das atividades que teríamos, como hackathons, todas ou foram online ou foram canceladas. Eu sou do board de organização da PyCon Sweden e esse ano já definimos que será online, e copiado como foi feita com a bela experiência da conferência Pyjamas, criado no Brasil. Mesmo que por milagre a doença amenize, ninguém acredita que as todos se sentirão seguros em estar em um aglomerado com 200 a 500 pessoas juntas. Não esse ano. E talvez nunca mais.
Como nossa organização sempre foi online, pouca coisa mudou. Mantemos nossas reuniões bi-semanais por conferência via browser e salvamos as minutas no github. Usamos o telegram pra conversas rápidas e mesmo manter os interessados na conferência a par do que está sendo feito.
E seguimos em frente.
Então acho que era isso que eu tinha pra descrever sobre como estamos enfrentando a pandemia aqui na Suécia. A receita parece simples, mas por aqui as coisas são levadas à sério. Então se quiserem seguir o exemplo sueco minha dicas são: fiquem em casa o máximo que puderem, ao sair de casa mantenham a distância de 2 metros ou mais uns dos outros, caso isso seja difícil usem máscaras, e lavem bem as mãos e com bastante frequência.
Hélio Loureiro
No Brasil muita gente já está preparando pra pular carnaval, mesmo com pandemia, e eu aqui aparecendo só agora com uma análise de 2020. Era pra ter feito lá pro fim ou começo do ano? Com certeza. Mas quem sou eu pra conseguir manter um ritmo desses?
Então vamos ver os dados.
Esse foi o tráfego anual. Nada de muita novidade. Média de 10 pessoas acessando por dia. Algum repique em maio (o que fiz em maio?) mas nada assombroso. Quase 3/4 do tráfego vindo de... busca orgânica? Quase 20% de links diretos (imagino que posts no twitter e no telegram). É... não foi um ano muito bom em tráfego mas a culpa é toda minha. Fui bem negligente quanto ao conteúdo. Acho que postei 4 ou 5 artigos só durante 2020.
Dos países que acessaram, Brasil ganha de longe. Ainda bem pois o conteúdo é em geral na língua tupiniquim. Mas interessante ver outros países com China e França na métrica, mesmo que tenham sido só uns 20 acessos cada.
Esses são os links mais acessados. A página orgânica é a frente do site. Interessante ver o interesse no artigo sobre o final da Linux Mall, que deixou muitas saudades. E... telnet via script? não achei que isso tivesse tanto interesse.
Maioria dos acessos vindo de desktops, com uma pequena parcela vindo de telefone. E alguns bem perdidos com tablet. Então toda a gritaria de avisos que o google manda sobre site não adaptado pra dispositivos móveis eu posso solenemente ignorar. Longa vida aos desktops!
E na guerra dos navegadores, Chrome nadando de braçadas. Firefox atrás mas muito atrás. Ao menos não vejo mais as estatísticas com vários acessos de Internet Explorer, se bem que ainda apareceram 19.
Em termos de sistemas operacionais... é... algumas coisas não mudam. Maioria dos acessos por Windows. Triste estatística. Interessante que tive 1 acesso de 1 Tizen.
E a linguagem do navegador? Maioria de brasileiros. Interessante notar Tchéccia (nem sei como escreve em português, mas é a antiga República Tcheca).
E é isso. Não um posto longo, mas apenas pra guardar de lembrança e comparar com o próximo em 2022.
Por muitos anos o meu comando preferido pra baixar páginas e até fazer mirrors foi o comando wget. Até eu descobrir o curl.
Não que eu já não conhecesse o curl. Com tantos anos de estrada com Unix, principalmente Linux e FreeBSD, curl era um conhecido que eu evitava usar. O motivo? Zona de conforto. wget era um comando conhecido e que eu sempre usava, desde que ando comecei a aprender Unix, em 1997. curl por outro lado era talvez tão antigo quanto, mas cheio de opções cabulosas que eu sentia um certo receio de usar.
Atualmente eu trabalho bastante com backend web e curl virou meu braço direito. E conforme fui aprendendo a usar mais, fui adorando. Hoje em dia não faço um container que seja sem o curl dentro. E acabei até deixando pra lá o wget mas... hoje é dia de falar de curl.
Durante essa semana no trabalho tivemos um problema de servidor web down. Ao tentar conectar ele ficava um tempo tentando dar a resposta e depois enviava um erro 500 (acho que era 503, mas isso não importa). Então como eu queria acessar o serviço assim qeu estivesse disponível e não queria ficar dando reload na aba do navegador, fiz um script com curl. Ele usa uma das opções do curl de retorna um valor do header de resposta, que no caso era os status code.
status_code=0
while [ $status_code -ne 200 ]
do
sleep 10
status_code=$(curl -s -o /dev/null -w "%{http_code}" https://helio.loureiro.eng.br/)
echo "status_code=$status_code"
done
O script fica monitorando o site e retorna o valor do status code em loop.
status_code=500 status_code=500 status_code=500 status_code=500 status_code=500 status_code=200
Assim que receber uma resposta 200, que é ok, para. Deixei isso rodando num shell no canto do desktop (uso duas telas então não bloqueou minha visão de nada). Assim que parou, voltei ao browser e acessei o serviço.
Coisa linda, não?
Se você tentou usar o pip, o gerenciador de módulos do python, nos últimos tempos então deve ter dado de cara com o seguinte erro:
/tmp > pip search conda ERROR: Exception: Traceback (most recent call last): File "/usr/local/lib/python3.6/dist-packages/pip/_internal/cli/base_command.py", line 224, in _main status = self.run(options, args) File "/usr/local/lib/python3.6/dist-packages/pip/_internal/commands/search.py", line 62, in run pypi_hits = self.search(query, options) File "/usr/local/lib/python3.6/dist-packages/pip/_internal/commands/search.py", line 82, in search hits = pypi.search({'name': query, 'summary': query}, 'or') File "/usr/lib/python3.6/xmlrpc/client.py", line 1112, in __call__ return self.__send(self.__name, args) File "/usr/lib/python3.6/xmlrpc/client.py", line 1452, in __request verbose=self.__verbose File "/usr/local/lib/python3.6/dist-packages/pip/_internal/network/xmlrpc.py", line 46, in request return self.parse_response(response.raw) File "/usr/lib/python3.6/xmlrpc/client.py", line 1342, in parse_response return u.close() File "/usr/lib/python3.6/xmlrpc/client.py", line 656, in close raise Fault(**self._stack[0]) xmlrpc.client.Fault: <Fault -32500: "RuntimeError: PyPI's XMLRPC API has been temporarily disabled due to unmanageable load and will be deprecated in the near future. See https://status.python.org/ for more information.">
Olhando no link apontado pelo erro, temos uma bela mensagem de erro.
As mensagens no site são as seguintes:
Traduzindo em miúdos: o site não está aguentando o tráfego. Simples assim.
Não vou entrar no mérito de como o site foi feito, se com flash, django, ou o que quer que seja que esse não é o ponto. O ponto é que fizeram uma péssima arquitetura. Um único ponto de controle que não sustenta o tráfego.
Quantos anos existe o Debian Mais de 25 já. O Debian criou uma solução pra isso já faz mais de uma década: repositórios com mirrors e pacotes assinados. Ao rodar o comando apt ou apt-get, uma listagem dos arquivos disponíveis é verificada. Se a informação do arquivo for a mesma do arquivo local, não é baixado. Isso diminui em muito a carga em cima dos servidores.
Já os pacotes pip usam um formato xml que provavelmente é baixado toda vez. Não só isso: não existe um pip stable, unstable e testing. A cada nova pequena versão do pacote, um novo arquivo é gerado. Isso torna cache dessa list impossível de ser mantido por qualquer mirror. E é o mesmo problema que se encontra em npm, compose, etc.
O contraponto de ter esse arquivo sempre atualizado é ter as versões mais recentes dos pacotes, módulos ou classes que se esteja usando. Mas o custo é alto em termos de capacidade de rede pra aguentar esse tráfego. O resultado é esse que vemos com o pip.
Terá solução? Acredito que sim. Mas sem re-pensar na arquitetura do pip, como receber módulos novos e atualizações, vai ser apenas como enxugar gelo.
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.
Page 9 of 33