DevOps, Ubuntu

Selecionando o repositório mais próximo do Ubuntu via linha de comando

Existem vários casos de uso em que você desejaria trocar a lista de repositórios para um repositório local mais próximo de você.

Em uma instalação padrão, o Ubuntu vai utilizar o país selecionado para pré-fixar um “country code” a url no /etc/apt/sources.list, no entanto esse comportamento não é ideal. Ao utilizar máquinas virtuais localmente com imagens já prontas, por exemplo usando o Vagrant, o problema se torna ainda pior, pois nesses casos, você provavelmente vai estar usando um repositório fora do país.

Estava tentando automatizar a alteração dos repositórios, e pelo fato de ser um projeto distribuído onde usuários de outros países iriam se beneficiar daquela configuração do Vagrant, não podia simplesmente fixar um país específico ou um repositório local.

O arquivo com a listagem de repositórios padrão da maioria das imagens do Ubuntu no Vagrant se parecem com a seguinte:

deb http://archive.ubuntu.com/ubuntu trusty universe
deb-src http://archive.ubuntu.com/ubuntu trusty universe
deb http://archive.ubuntu.com/ubuntu trusty-updates universe
deb-src http://archive.ubuntu.com/ubuntu trusty-updates universe

## N.B. software from this repository is ENTIRELY UNSUPPORTED by the Ubuntu
## team, and may not be under a free licence. Please satisfy yourself as to
## your rights to use the software. Also, please note that software in
## multiverse WILL NOT receive any review or updates from the Ubuntu
## security team.
# deb http://archive.ubuntu.com/ubuntu trusty multiverse
# deb-src http://archive.ubuntu.com/ubuntu trusty multiverse
# deb http://archive.ubuntu.com/ubuntu trusty-updates multiverse
# deb-src http://archive.ubuntu.com/ubuntu trusty-updates multiverse

## Uncomment the following two lines to add software from the 'backports'
## repository.
## N.B. software from this repository may not have been tested as
## extensively as that contained in the main release, although it includes
## newer versions of some applications which may provide useful features.
## Also, please note that software in backports WILL NOT receive any review
## or updates from the Ubuntu security team.
# deb http://archive.ubuntu.com/ubuntu trusty-backports main restricted universe multiverse
# deb-src http://archive.ubuntu.com/ubuntu trusty-backports main restricted universe multiverse

## Uncomment the following two lines to add software from Canonical's
## 'partner' repository.
## This software is not part of Ubuntu, but is offered by Canonical and the
## respective vendors as a service to Ubuntu users.
# deb http://archive.canonical.com/ubuntu trusty partner
# deb-src http://archive.canonical.com/ubuntu trusty partner

deb http://security.ubuntu.com/ubuntu trusty-security main
deb-src http://security.ubuntu.com/ubuntu trusty-security main
deb http://security.ubuntu.com/ubuntu trusty-security universe
deb-src http://security.ubuntu.com/ubuntu trusty-security universe
# deb http://security.ubuntu.com/ubuntu trusty-security multiverse
# deb-src http://security.ubuntu.com/ubuntu trusty-security multiverse

Pesquisando pela internet, inicialmente me deparei com o um artigo do AskUbuntu, onde ele sugere duas abordagens possíveis:

1. Apontar no sources.list uma listagem de mirrors, como exemplo abaixo:

deb mirror://mirrors.ubuntu.com/mirrors.txt trusty main restricted universe multiverse  
deb mirror://mirrors.ubuntu.com/mirrors.txt trusty-updates main restricted universe multiverse  
deb mirror://mirrors.ubuntu.com/mirrors.txt trusty-backports main restricted universe multiverse  
deb mirror://mirrors.ubuntu.com/mirrors.txt trusty-security main restricted universe multiverse 

2. Utilizar a ferramenta netselect para verificar uma listagem de mirrors e alterar o sources.list usando sed.

Ambas abordagens tem problemas. Na primeira, existem relatos de falhas ocasionais, tornando a abordagem não confiável e portanto descartada. Já no caso da segunda, ele leva em consideração apenas a latência, sendo que no caso dos mirrors existem outros parâmetros úteis (banda, quão atrasado em relação ao oficial, etc).

Durante a minha pesquisa, haviam críticas sobre lugares onde o mirror selecionado que era mais perto não era o mais rápido, etc.

No final do artigo, um script em Python que leva em consideração as informações de status dos mirrors contidas na listagem oficial, acabou sendo a melhor opção.

O problema agora era como automatizar isso dentro do meu Vagrantfile. A primeira tentativa envolvia atualizar o apt, instalar as dependências, instalar o git, clonar o repositório e então rodar na mão.

O problema é que esse procedimento seria executado com o mirror original, e por isso ainda não era ideal. A alternativa correta seria empacotar a ferramenta, baixar o pacote e instalar como primeiro procedimento.

Foi exatamente isso que fiz.

Criando pacotes Debian

Criar pacotes Debian (.deb) na mão é um dos processos mais chatos que alguém pode fazer, pois é extremamente burocrático. Existe uma infinidade de arquivos e pastas que precisam ser criadas com conteúdos específicos seguindo um formato específico, e pra ajudar a documentação é extensa, complexa e em muitos casos desatualizada.

Por sorte existem dois projetos que podem nos ajudar aqui, o primeiro é o FPM: “Effing package management! Build packages for multiple platforms (deb, rpm, etc) with great ease and sanity”.

O FPM surgiu da frustração, do trabalho repetitivo que o autor original dele passava ao ter que criar pacotes, para mais de uma distribuição linux. Ele torna possível, com um comando simples e poucos parâmetros, gerar pacote para qualquer distribuição e mais alguns outros formatos.

O segundo projeto é um “syntax sugar” em cima do FPM, que se parece muito com receitas do Homebrew, o FPM Cookery.

Este é um exemplo simplificado da receita que escrevi, com o mínimo para que seja empacotado:

class AptSelect < FPM::Cookery::Recipe
  name        'apt-select'
  version     '0.1.0'
  revision    0
  homepage    'https://github.com/jblakeman/apt-select'
  license     'MIT'
  description 'Choose a fast, up to date Ubuntu apt mirror'
  maintainer  'Gabriel Mazetto <brodock@gmail.com>'
  source      './', :with => :local_path
  arch        'all'

  platforms [:ubuntu] do
    depends 'python-bs4'
  end

  def build
  end

  def install
    share('apt-select').mkdir
    share('apt-select/bin').mkdir
    Dir["#{workdir}/*"].each { |f| share('apt-select').install f if allowed_file?(f) }
    Dir["#{workdir}/bin/*"].each { |f| share('apt-select/bin').install f }
  end

  private 

  def allowed_file?(file)
    allowed_formats = %w(.py .md .sh)
    allowed_formats.include? File.extname(file)
  end
end

As primeiras informações da receita são os metadados do pacote, além da origem dos arquivos (eu optei por uma receita auto-contida no repositório, mas poderia baixar um tarball, clonar um repositório etc).

Como se trata de um código Python, não existe a necessidade de compilação, por isso a etapa de build está vazia.

O install é onde ocorre a criação de pastas e selecionamos pra onde os arquivos vão. Neste exemplo criei também um helper method allowed_file? pra selecionar programaticamente os arquivos de interesse.

O código final pode ser visto nesse pull-request, e quem quiser pegar o arquivo já empacotado, pode encontrar aqui.

Utilizando o apt-select

Com o pacote baixado e instalado, ao rodar o comando apt-select, ele vai pesquisar na lista de mirrors e nas informações de status, para determinar qual o melhor, e gerar um arquivo sources.list na pasta onde você executou o comando inicialmente.

Existe um segundo comando no pacote que é o apt-select-update, este deve ser executado após o primeiro, usando sudo, e ele será responsável por trocar o oficial do sistema, fazer um backup e substituir pela versão otimizada.

Com os comandos a seguir, podemos fazer a instalação a busca pelo melhor mirror e substituir o atual do sistema:

curl -s -L -O https://github.com/brodock/apt-select/releases/download/0.1.0/apt-select_0.1.0-0_all.deb && sudo dpkg -i apt-select_0.1.0-0_all.deb
apt-select && sudo apt-select-update

Os pacotes funcionam tanto em i386 (32 bits) quanto amd64 (64 bits), e foram testados no Ubuntu 12.04 e 14.04, mas devem funcionar em outras versões também.

Padrão