Programação Por Equipe DevBras

Capa do artigo: Resposta antiga de fórum: 3 comandos que não rodam mais

Resposta antiga de fórum: 3 comandos que não rodam mais

Comandos de fórum que quebraram no Debian 13, pip e PHP 8, com o substituto oficial testado e um script que data cada resposta do Stack Overflow antes de colar.

Três comandos que aparecem no topo da busca quando alguém configura um servidor Debian hoje: apt-key add para cadastrar um repositório, sudo pip install para instalar uma biblioteca Python e mysql_connect num tutorial de PHP. Os três foram rodados neste mês num Debian 13 limpo e num PHP 8.4. Nenhum funcionou. O que falha não é o fórum, é a data: cada resposta estava certa para a versão de quando foi escrita.

Este post mostra os três casos com a saída real do terminal, o substituto atual de cada um conferido na documentação oficial e um script curto que puxa, pela API pública do Stack Exchange, a data de criação e da última edição de cada resposta, além de apontar comandos que pedem leitura cuidadosa.

Caso 1: a chave do repositório com apt-key

A instrução clássica para adicionar o repositório do Docker (ou de quase qualquer fornecedor) era baixar a chave e jogar no chaveiro global do APT. Num contêiner debian:13 com APT 3.0.3, em outubro de 2026:

$ curl -fsSL https://download.docker.com/linux/debian/gpg | apt-key add -
bash: line 4: apt-key: command not found

O comando não está mais lá. E o problema dele nunca foi só estético: uma chave no chaveiro global vale para todos os repositórios configurados, então a chave de um fornecedor podia validar pacotes que chegassem por outro. O formato atual amarra cada chave a um repositório com Signed-By, e a documentação oficial do Docker para Debian já usa o arquivo .sources no formato deb822:

sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc
. /etc/os-release
sudo tee /etc/apt/sources.list.d/docker.sources <<EOF
Types: deb
URIs: https://download.docker.com/linux/debian
Suites: $VERSION_CODENAME
Components: stable
Architectures: $(dpkg --print-architecture)
Signed-By: /etc/apt/keyrings/docker.asc
EOF
sudo apt-get update

No teste, o apt-get update baixou o índice do Docker e o apt-cache policy docker-ce mostrou o candidato 5:29.9.2-1~debian.13~trixie. Para provar que a amarração funciona, o Signed-By foi apontado para outra chave (a do próprio Debian). O APT recusou o repositório:

Err:4 https://download.docker.com/linux/debian trixie InRelease
  Sub-process /usr/bin/sqv returned an error code (1), error message is:
  Missing key 7EA0A9C3F273FCD8, which is needed to verify signature.

É esse o comportamento que você quer: chave errada, repositório fora. A página do Debian sobre repositórios de terceiros descreve o mesmo modelo, e a man page sources.list(5) do trixie documenta os campos do deb822. Se o servidor também vai rodar contêineres, o post Docker no servidor: o primeiro container não é a arquitetura inteira continua daqui.

Caso 2: sudo pip install no Python do sistema

Respostas de 2015 a 2020 mandam instalar biblioteca com sudo pip install. No Debian 13, com pip 25.1.1 e Python 3.13:

$ pip install requests
error: externally-managed-environment

× This environment is externally managed
╰─> To install Python packages system-wide, try apt install
    python3-xyz, where xyz is the package you are trying to
    install.

O bloqueio vem da PEP 668: o Python do sistema pertence ao gerenciador de pacotes da distribuição, e um pip solto por cima pode quebrar ferramentas do próprio sistema que dependem de versões específicas. A especificação no guia de empacotamento do Python descreve o arquivo EXTERNALLY-MANAGED que liga essa trava. O caminho atual é um ambiente virtual por aplicação, com o módulo venv:

python3 -m venv /opt/app/.venv
/opt/app/.venv/bin/pip install requests
/opt/app/.venv/bin/python -c "import requests; print(requests.__version__)"
# 2.34.2

Este caso mostra outra armadilha. A pergunta mais votada sobre esse erro no Stack Overflow tem respostas recentes, e quatro das mais votadas citam --break-system-packages. A opção existe, mas o nome já avisa o efeito: ela desliga a proteção que a PEP 668 criou. Resposta nova não é sinônimo de resposta segura. É preciso ler se ela recomenda a opção ou só explica por que evitá-la.

Caso 3: mysql_connect no tutorial de PHP

Tutorial de login com mysql_connect e mysql_query ainda circula em blog espelhado. No PHP 8.4.27:

$ php -r 'mysql_connect("localhost", "u", "p");'
PHP Fatal error:  Uncaught Error: Call to undefined function mysql_connect()

A extensão saiu no PHP 7.0, como registra o guia de migração do PHP 7.0, e a própria página da função no manual manda usar MySQLi ou PDO. O risco aqui é o conserto apressado: trocar mysql_query por mysqli_query e manter a consulta montada com o texto do formulário. O erro some e a injeção de SQL fica. A troca correta usa PDO com parâmetro:

$pdo = new PDO('mysql:host=localhost;dbname=loja;charset=utf8mb4', $usuario, $senha, [
    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
]);
$stmt = $pdo->prepare('SELECT id, senha_hash FROM usuarios WHERE email = ?');
$stmt->execute([$_POST['email'] ?? '']);
$linha = $stmt->fetch(PDO::FETCH_ASSOC);

O post sobre tutorial de login PHP antigo percorre o resto da migração, incluindo o hash da senha.

Datar a resposta antes de ler o código

Nos três casos a pista estava na data, e o Stack Exchange expõe isso numa API pública que não exige cadastro para uso leve (sem chave de aplicativo, a cota que o teste mostrou foi de 300 chamadas por dia, e o próprio script imprime quanto resta). O script abaixo recebe o link de uma pergunta do Stack Overflow, Ask Ubuntu, Server Fault ou Super User e lista as respostas por voto, com data de criação, data da última edição e um aviso quando o código cita algo que hoje pede cuidado:

#!/usr/bin/env python3
"""Data e sinais de alerta das respostas de uma pergunta do Stack Exchange."""
import gzip, html, json, re, sys, urllib.request
from datetime import datetime, timezone

ALERTAS = {
    r"\bapt-key\b": "cita apt-key (removido no apt do Debian 13)",
    r"\bcurl\s+(-\w*k\b|--insecure)": "curl sem verificar certificado",
    r"sslVerify\s+false": "git sem verificar certificado",
    r"verify\s*=\s*False": "Python sem verificar certificado",
    r"chmod\s+(-R\s+)?777": "chmod 777",
    r"--break-system-packages": "cita --break-system-packages",
    r"\bmysql_(connect|query)\b": "funções mysql_* (removidas no PHP 7)",
    r"setenforce\s+0": "SELinux desligado",
}

def dia(ts):
    return datetime.fromtimestamp(ts, timezone.utc).strftime("%Y-%m-%d") if ts else "nunca"

def main(url):
    m = re.search(r"https?://([^/]+)/questions/(\d+)", url)
    if not m:
        sys.exit("use o link da pergunta: https://site/questions/NUMERO/...")
    host, qid = m.groups()
    site = host.removesuffix(".com").removesuffix(".stackexchange")
    api = (f"https://api.stackexchange.com/2.3/questions/{qid}/answers"
           f"?site={site}&sort=votes&order=desc&pagesize=10&filter=withbody")
    with urllib.request.urlopen(api, timeout=20) as r:
        bruto = r.read()
    if bruto[:2] == b"\x1f\x8b":  # a API costuma responder em gzip
        bruto = gzip.decompress(bruto)
    dados = json.loads(bruto)
    for a in dados["items"]:
        codigo = " ".join(re.findall(r"<code>(.*?)</code>", a["body"], re.S))
        codigo = html.unescape(codigo)
        achados = [nome for rx, nome in ALERTAS.items() if re.search(rx, codigo)]
        print(f"{a['answer_id']:>9}  votos {a['score']:>4}  "
              f"{'aceita ' if a['is_accepted'] else '       '}"
              f"criada {dia(a['creation_date'])}  "
              f"editada {dia(a.get('last_edit_date'))}")
        for nome in achados:
            print(f"{'':11}alerta: {nome}")
    print(f"cota restante da API hoje: {dados['quota_remaining']}")

if __name__ == "__main__":
    main(sys.argv[1])

Rodando contra a pergunta do Ask Ubuntu sobre o substituto do apt-key:

$ python3 datar.py https://askubuntu.com/questions/1286545/
  1307181  votos  368         criada 2021-01-11  editada 2026-10-07
           alerta: cita apt-key (removido no apt do Debian 13)
  1300076  votos   17  aceita criada 2020-12-14  editada nunca
  1303285  votos   13         criada 2020-12-28  editada 2023-02-19
           alerta: cita apt-key (removido no apt do Debian 13)
  1321263  votos   12         criada 2021-03-05  editada 2022-10-13
           alerta: cita apt-key (removido no apt do Debian 13)
  1412554  votos    8         criada 2022-06-06  editada 2024-06-26
  1400468  votos    7         criada 2022-04-01  editada 2022-06-07
           alerta: cita apt-key (removido no apt do Debian 13)
  1306494  votos    0         criada 2021-01-09  editada 2021-01-09
           alerta: cita apt-key (removido no apt do Debian 13)
  1470372  votos    0         criada 2023-06-02  editada nunca
cota restante da API hoje: 293

A leitura que importa está nas colunas, não nos votos. A resposta aceita é de dezembro de 2020 e nunca foi editada. A mais votada é de janeiro de 2021, mas foi editada em outubro de 2026, ou seja, alguém continua mantendo o texto. O aviso cita apt-key aparece nela porque ela explica a troca, não porque recomenda o comando; o script aponta onde ler com atenção, não decide sozinho. Contra a pergunta do erro externally-managed-environment, as quatro primeiras respostas recebem o aviso de --break-system-packages:

 75722775  votos  795         criada 2023-03-13  editada 2025-12-17
           alerta: cita --break-system-packages
 76641565  votos  342         criada 2023-07-08  editada 2026-07-27
           alerta: cita --break-system-packages
 78652149  votos  227         criada 2024-06-21  editada 2025-12-15
           alerta: cita --break-system-packages
 76540031  votos  181         criada 2023-06-23  editada 2025-12-14
           alerta: cita --break-system-packages
 76672519  votos   63         criada 2023-07-12  editada 2024-06-06

O que conferir na sua máquina antes de colar

Com a data em mãos, o passo seguinte é comparar com o que está instalado, não com o que você lembra que está instalado:

cat /etc/os-release | head -3   # distribuição e versão
apt --version                   # apt 3.0.3 no Debian 13
python3 --version; pip --version
php -v | head -1
node -v

Se a resposta é anterior a um salto de versão (Debian 12 para 13, PHP 5 para 7, Python sem venv para PEP 668), procure o nome exato do comando no manual da versão instalada e no guia de migração entre as duas. Quando o manual diz removido ou obsoleto, a resposta terminou ali, por mais votos que tenha.

Alguns comandos merecem parada obrigatória, independentemente da data: curl -k ou --insecure, git config http.sslVerify false e verify=False no Python aceitam qualquer servidor fingindo ser o verdadeiro; chmod 777 dá escrita a qualquer processo da máquina; setenforce 0 desliga o SELinux inteiro para resolver uma permissão. Do lado do Windows, o equivalente é desligar o Defender para o instalador passar, e o post Defender bloqueou o instalador? Libere o arquivo mostra a liberação pontual que resolve sem abrir a máquina.

Por fim, teste num contêiner descartável antes do servidor. Os três casos deste post foram reproduzidos assim, com docker run --rm debian:13, e o contêiner some ao sair. Ao aplicar no servidor de verdade, anote o link da resposta e a data no commit ou no ticket: quando alguém estranhar aquela linha daqui a dois anos, vai saber de onde ela veio e de quando ela era.

Avalie este artigo

Referência: https://br.answers.yahoo.com/