___  ___           _         ______ _                  _       
|  \/  |          | |        | ___ (_)         _      (_)      
| .  . | ___ _ __ | |_  ___  | |_/ /_ _ __   _//_ _ __ _  __ _ 
| |\/| |/ _ \ '_ \| __|/ _ \ | ___ \ | '_ \ / _` | '__| |/ _` |
| |  | |  __/ | | | |_|  __/ | |_/ / | | | | (_| | |  | | (_| |
\_|  |_/\___|_| |_|\__|\___| \____/|_|_| |_|\__,_|_|  |_|\__,_|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~www.mentebinaria.com.br

0x19 - Como criar um pacote deb "na unha"

por Fernando Mercês <fernando *em* mentebinaria.com.br>
** Licenciado sob a Creative Commons 3.0 **

Aproveitando que farei uma apresentação sobre pacotes deb na comemoração do 18º aniversário do Debian GNU/Linux, decidi escrever este artigo, para mostrar o que é um pacote deb e como ele pode ser gerado de forma quase artesanal.

O objetivo deste artigo é mostrar o que é um pacote deb e como ele se comporta. Mostrarei como olhar o conteúdo de um pacote deb a fim de copiar sua estrutura para gerar um novo, coisa que fiz quando comecei a me interessar pelo assunto.

Este artigo faz parte de um objetivo maior, que é atrair colaboradores para a comunidade Debian. Um pacote "caseiro" não será aceito nos repositórios oficiais, mas iniciará o leitor no mundo do empacotamento e, quem sabe, despertará o Debian Maintainer que há em você. :)

Índice

1. Introdução
2. Pacote "caseiro" x pacote oficial
3. Do que é feito o pacote deb
4. Extraindo um pacote deb
5. Criando um pacote "artesanal"
6. Conclusão

1. Introdução

Imagine um mundo sem pacotes deb. Todos os softwares livres seriam distribuídos em código-fonte. Você seria obrigado a baixar, compilar e instalar. Fácil? Nem sempre. Os programas possuem dependências de bibliotecas que o desenvolvedor usou. Na máquina dele essas bibliotecas estão instaladas, na sua provavelmente não. Então seria preciso instalá-las antes de compilar o programa que você quer.

Claro que os jargões utilizados aqui só fazem sentido para usuários avançados. O Linux não teria conquistado a popularidade que tem hoje se os usuários leigos tivessem que compilar o Firefox para navegar na internet. Os pacotes supriram esta necessidade e hoje um software inteiro com suas dezenas de dependências pode ser instalado graficamente ou com uma curta e simples linha de comando. Cabe aqui um viva ao software livre. \o/

Este artigo não é para leigos, mas para quem gostaria de entender como o pacote deb funciona.

2. Pacote "caseiro" x pacote oficial

Para fazer um pacote e submetê-lo ao repositório oficial, você precisa ler, compreender e seguir rígidas regras de qualidade, que podem ser encontradas nos documentos oficiais [1]. No entanto, creio que o modo mais fácil é começar por um pacote "caseiro", que não segue a política, para entender o funcionamento e então partir para o estudo dos documentos. Foi assim que deu certo comigo e hoje mantenho o pacote do pev [2] no repositório oficial (testing).

A comunidade Debian oferece uma série de ferramentas para criação de pacotes, sempre com foco nos pacotes oficiais. Neste artigo evitarei usar tais ferramentas, mas vou comentar, para que o leitor já se ambiente.

3. Do que é feito o pacote deb

Se você der uma olhada em no diretório /var/cache/apt/archives provavelmente vai encontrar vários pacotes deb. Eles estão lá porque foram baixados por você seja via Synaptic, apt-get, aptitude ou outro gerenciador de pacotes que use o dpkg.

Antes de usar o dpkg (a ferramenta oficial) para analisar um pacote deb, vamos ver do que um pacote deb é feito. Escolhi como exemplo o pacote do wget.

$ ls -lh wget*
-rw-r--r-- 1 fernando fernando 717K Aug 17 00:26 wget_1.12-5_amd64.deb Vamos copiar o pacote para o /tmp, para manter o cache intacto:
$ cp wget_1.12-5_amd64.deb /tmp No diretório /tmp, podemos usar o comando file para ver o tipo de arquivo do pacote deb:
$ cd /tmp
$ file wget_1.12-5_amd64.deb
wget_1.12-5_amd64.deb: Debian binary package (format 2.0)

A libmagic (usada pelo file) reconhece o pacote corretamente. Mas será que os desenvolvedores criaram realmente um tipo de arquivo completamente novo para armazenar o conteúdo de um programa? Sabemos que dentro de um pacote deb há os arquivos executáveis do programa, documentação, ícones etc. Não seria viávei utilizar um agrupador de arquivos com compressão ou coisa do tipo?

Eric Raymond, um dos hackers mais respeitados do mundo detém a seguinte crença, escrita em seu documento "How to become a hacker" [3]: "No problem should ever have to be solved twice" (Nenhum problema deve ser resolvido duas vezes). Ou seja, não é preciso "reinventar a roda", como dizemos popularmente. Com base nesta inteligente frase, os desenvolvedores do dpkg e do formato deb usaram sim o que já exitia para atingir seus objetivos, o que foi brilhante.

Na página de manual do formato deb(5), podemos ler:

"The file is an ar archive with a magic value of !<arch>."

Para conferir, comande:
$ man deb

Então estamos falando de um arquivo ar [4]. Conforme você pode ver na referência, ar é um utilitário do conjunto binutils, do projeto GNU, para criar, modificar e extrair arquivos. É um agrupador, assim como o tar.

Vamos conferir o magic value como disse o man?

$ hd -n 64 wget*
00000000  21 3c 61 72 63 68 3e 0a  64 65 62 69 61 6e 2d 62  |!<arch>.debian-b|
00000010  69 6e 61 72 79 20 20 20  31 33 31 31 34 35 31 34  |inary   13114514|
00000020  35 31 20 20 30 20 20 20  20 20 30 20 20 20 20 20  |51  0     0     |
00000030  31 30 30 36 34 34 20 20  34 20 20 20 20 20 20 20  |100644  4       |

Certinho. O tal "!<arch>" esta no início do arquivo, o que sugere ser o seu magic number. Vamos listar o conteúdo deste arquivo com a ferramenta ar então:

$ ar tv wget*
rw-r--r-- 0/0      4 Jul 23 17:04 2011 debian-binary
rw-r--r-- 0/0   2432 Jul 23 17:04 2011 control.tar.gz
rw-r--r-- 0/0 731281 Jul 23 17:04 2011 data.tar.gz

Então temos um arquivo ar com três arquivos. E se criarmos um ar com um arquivo qualquer, o que será que o file retorna?

$ echo "meu texto" > texto.txt

$ ar r teste.deb texto.txt
ar: creating teste.deb

O comando acima criou um arquivo ar chamado teste.deb (lembre-se que o Linux despreza extensões) e adicionou o arquivo texto.txt nele.

$ file teste.deb
teste.deb: current ar archive

O file retorna que é um arquivo ar, corretamente.

Mas por que o file reconhece como pacote do wget corretamente como binário do debian? A resposta está no código-fonte do file [5], que identifica um pacote binário se depois do magic number do ar, o arquivo contiver a string "debian-binary". De fato, o formato ar define o magic e o nome do primeiro arquivo agrupado em seguida. Então bastaria que criássemos um arquivo ar com um arquivo debian-binary qualquer para o "enganar" o file.

$ echo 2011 > debian-binary

$ ar r fake.deb debian-binary
ar: creating fake.deb $ file fake.deb
fake.deb: Debian binary package (format 2011) Agora sim. O file não faz nenhuma verificação adicional (e nem deveria). Mas a intenção aqui não é hackear o file (até porque estamos falando de um formato livre, com extensa documentação), e sim criar um deb "na mão". Perceba que o formato apareceu como "2011". Claro que é um pacote inválido e se você tentar
instalar o dpkg vai gerar um erro. Nem perca seu tempo. ;)

4. Extraindo um pacote deb

Voltando ao que interessa, vamos extrair o conteúdo do pacote deb do wget para conferir o que há de interessante:

$ mkdir wget
$ cd wget
$ ar xv ../wget*
x - debian-binary
x - control.tar.gz
x - data.tar.gz $ cat debian-binary
2.0

Exatamente o que o file informou. Versão 2.0 do formato deb. Beleza.

$ tar tvzf control.tar.gz
drwxr-xr-x root/root         0 2011-07-23 17:04 ./
-rw-r--r-- root/root      3832 2011-07-23 17:04 ./md5sums
-rw-r--r-- root/root        12 2011-07-23 17:04 ./conffiles
-rw-r--r-- root/root      1327 2011-07-23 17:04 ./control

Acima vemos alguns arquivos de controle do pacote. O interesse maior é no arquivo "control", necessário para um pacote funcionar. O md5sums também é legal de se ter.

$ tar tvzf data.tar.gz

-rw-r--r-- root/root       651 2009-09-21 23:52
./usr/share/doc/wget/ChangeLog.README
drwxr-xr-x root/root         0 2011-07-23 17:04 ./usr/bin/
-rwxr-xr-x root/root    353824 2011-07-23 17:04 ./usr/bin/wget

Já o data.tar.gz contém os dados do pacote em si, incluindo binários executáveis e documentação, todos numa estrutura bem definida. Aliás, é esta estrutura que o pacote cria ao ser instalado.

Estou mais interessado no control.tar.gz. Vamos extraí-lo:

$ tar xvzf control.tar.gz
./
./md5sums
./conffiles
./control $ head -5 md5sums
1b2acae8540b64a3170dc4ce0200809e  usr/bin/wget
d62b0aafbbacf1d54031ded4d1a5f232  usr/share/doc/wget/AUTHORS
2f58d6d92cabcf358718a564d3e132d4  usr/share/doc/wget/ChangeLog.README
2b95a82f1c7499025d67ff86af2d7ecd  usr/share/doc/wget/MAILING-LIST
9e83cee67a496f5eb62aecf283e14367  usr/share/doc/wget/NEWS.gz

Certo, vemos no arquivo md5sums, o hash MD5 de cada arquivo incluso no data.tar.gz (no entanto só imprimi 5 linhas com o head). Não seria difícil gerar isso para o nosso pacote "artesenal".

$ cat control
Package: wget
Version: 1.12-5
Architecture: amd64
Maintainer: Noël Köthe
Installed-Size: 2344
Depends: libc6 (>= 2.3), libidn11 (>= 1.13), libssl1.0.0 (>= 1.0.0), dpkg (>=
1.15.4) | install-info
Conflicts: wget-ssl
Section: web
Priority: important
Multi-Arch: foreign
Homepage: http://www.gnu.org/software/wget/
Description: retrieves files from the web
Wget is a network utility to retrieve files from the web
using HTTP(S) and FTP, the two most widely used internet
protocols. It works non-interactively, so it will work in
the background, after having logged off. The program supports
recursive retrieval of web-authoring pages as well as ftp
sites -- you can use wget to make mirrors of archives and
home pages or to travel the web like a WWW robot.
.
Wget works particularly well with slow or unstable connections
by continuing to retrieve a document until the document is fully
downloaded. Re-getting files from where it left off works on
servers (both HTTP and FTP) that support it. Both HTTP and FTP
retrievals can be time stamped, so wget can see if the remote
file has changed since the last retrieval and automatically
retrieve the new version if it has.
.
Wget supports proxy servers; this can lighten the network load,
speed up retrieval, and provide access behind firewalls.

Este é o arquivo que descreve o pacote. A referência deste arquivo é tratada na Debian Policy [6], mas os campos mais comumente usados para pacotes simples são:

Package  - Nome do pacote
Version  - Versão do programa-versão do pacote
Architecture  - Arquitetura para qual o programa foi compilado. Pode ser i386, amd64, dentre outras. Pode ser "all" para scripts, por exemplo.
Maintainer  - Aqui vai o seu nome e e-mail. O criador do pacote (não do programa a ser empacotado).
Installed-Size - O espaço estimado, em bytes, requerido para instalar o pacote.
Depends  - Os pacotes dos quais seu pacote depende. Se for um programa em Python, vai depender do interpretador python, por exemplo.
Homepage  - O site do programa empacotado
Description  - A descrição do pacote. Uma linha para a descrição curta e demais para descrição longa, onde todas devem começar com um espaço e as linhas em branco devem possuir um ponto (.), que não vai aparecer no final das contas.

Agora que já explicamos a estrutura de um pacote deb básico, vamos ver como criar um.

5. Criando um pacote "artesanal"

Precisamos ter o que empcotar. Então vamos criar um software de teste.

$ mkdir /tmp/nada-1.0
$ cd /tmp/nada-1.0

$ echo -e "#include <stdio.h>\n\nint main()\n{\n\tputs(\"nada...\");\n\treturn 0;\n}" > nada.c

Os comandos acima devem criar um arquivo nada.c, no diretório /tmp/nada-1.0, com o seguinte conteúdo:

#include <stdio.h>

int main()
{
        puts("nada...");
        return 0;
}

Agora precisamos compilar o programa:

$ gcc -o nada nada.c

O binário "nada" precisa de uma estrutura. Então vamos colocá-lo num usr/bin:

$ mkdir -p usr/bin
$ mv nada usr/bin/

NOTA: Cuidado para não tentar mover o "nada" para o /usr/bin do sistema.

Agora temos a seguinte estrutura:

$ find .
. ./usr
./usr/bin
./usr/bin/nada
./nada.c

Precisamos do arquivo de controle. Que tal este?

Package: nada
Version: 1.0
Architecture: amd64
Maintainer: Você
Homepage: http://www.tacomnada.com
Installed-Size: 6560
Depends: libc6 (>= 2.2.5)
Description: program that does nothing
Na realidade o nada conseguie imprimir a string "nada..." na tela. Claro que
pode servir para algo, mas nao saberia dizer para que. Talvez para aprender
a empacotar no Debian, ou para nao fazer nada.
.
Depois de falar tanto, nao vou dizer mais nada.

Basta salvar como control (sem extensão mesmo).

Para o campo Installed-Size, eu contei os bytes do binário "nada":

$ wc -c nada
6560 nada

Já no campo Depends, é interessante avaliar a saída do ldd:

$ ldd nada
        linux-vdso.so.1 => (0x00007fffea5ff000)
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fcebbf5d000)
        /lib64/ld-linux-x86-64.so.2 (0x00007fcebc2fe000)

A primeira entrada é a uma biblioteca virtual para interface com o kernel. Ela sempre existirá e também pode aparecer como linux-gate.so.1.

Em seguida temos duas bibliotecas reais. Supondo que não saibamos em qual pacote elas se encontram, podemos usar o apt-file:

$ apt-file search libc.so.6
libc6: /lib/x86_64-linux-gnu/libc.so.6
libc6-i386: /lib32/libc.so.6

O pacote de nosso interece é o libc6. Agora, a versão da biblioteca requerida vai depender das funções que o programador utilizou. Isto consultado ser pego no site do projeto ou diretamente com o desenvolvedor. Usei 2.2.5.

Vamos buscar a última lib que o ldd detectou:

$ apt-file search ld-linux-x86-64.so.2
libc6: /lib/ld-linux-x86-64.so.2
libc6: /lib/x86_64-linux-gnu/ld-linux-x86-64.so.2

Também está no pacote libc6. Beleza, então só temos esta dependência.

Agora precisamos gerar os arquivos control.tar.gz (com o control dentro), data.tar.gz (com toda a árvode usr dentro) e debian-binary (com a string 2.0 dentro):

$ tar cvzf data.tar.gz usr/
usr/
usr/bin/
usr/bin/nada $ tar cvzf control.tar.gz control
control

$ echo 2.0 > debian-binary

$ ar r nada-1.0_amd64.deb debian-binary control.tar.gz data.tar.gz
ar: creating nada-1.0_amd64.deb
fernando@localhost:/tmp/wget/nada-1.0$ file nada-1.0_amd64.deb
nada-1.0_amd64.deb: Debian binary package (format 2.0)

Perceba que o primeiro arquivo a ser adicionado no ar é o debian-binary.

E agora vamos testar.

$ sudo dpkg -i nada-1.0_amd64.deb
Selecting previously deselected package nada.
(Reading database ... 146331 files and directories currently installed.)
Unpacking nada (from nada-1.0_amd64.deb) ...
Setting up nada (1.0) ... $ nada
nada... $ sudo dpkg -P nada
(Reading database ... 146332 files and directories currently installed.)
Removing nada ...

6. Conclusão

O pacote funciona. Inclusive eu fiz um script [7] para empacotar [quase que] desta forma o Evan's Debugger [8]. Vale a pena dar uma olhada, pois tem outros arquivos e comandos que não mencionei aqui. No entanto, ressalto que a preferência deve ser para pacotes com alta qualidade.

Provavelmente escreverei um outro artigo sobre a forma correta de se criar pacotes, com base na política do Debian e com as ferramentas providas pela comunidade. Não é fácil fazer um pacote de qualidade, mas o primeiro passo, na minha opinião, é entender com o que estamos lidando e espero que este artigo tenha atingido este objetivo.

[1] http://www.debian.org/doc/#manuals
[2] http://packages.debian.org/wheezy/pev
[3] http://catb.org/~esr/faqs/hacker-howto.html#believe2
[4] http://www.gnu.org/s/binutils/
[5] ftp://ftp.astron.com/pub/file/
[6] http://www.debian.org/doc/debian-policy/ch-controlfields.html
[7] http://www.mentebinaria.com.br/artigos/0x19/makedeb_v2.sh
[8] http://codef00.com/projects#debugger