Alpine Linux: Compilando

Saudações.

Este tutorial ensina como compilar os pacotes originais do Alpine Linux usando os esquemas oficiais.

O propósito e utilidade desses procedimentos serve à personalização de programas na estapa de compilação como:

  • Adicionar features omitidas no pacote oficial;
  • Adicionar configurações;
  • Ativar tunings de arquitetura (instruções nativas);
  • Criar repositórios de pacotes próprios.

Tutoriais de base:

Pré-requisitos:

  • VM/VPS/Host com instalação do Linux Alpine;
  • Internet no servidor (sua VPS ou host);

1 – Preparando Alpine

Vamos baixar os pacotes de ferramentas e fontes. Iniciaremos logados como root.

1.1 – Ferramentas

Programas compiladores:

Shell (root)
# Atualizar o sistema base
    apk update;
    apk upgrade;

# (No Host ou VM, se houver atualizacao de kernel, reinicie: reboot )

# Instalar ferramentas para o ambiente de compilacao:
    apk add \
        alpine-sdk abuild doas \
        sudo bash openssl tar \
        gnupg file grep binutils \
        linux-headers strace \
        git \
        make pkgconf patch \
        gcc g++ readline musl-dev musl-utils;

1.2 – Usuário para compilação

Os procedimentos de compilação devem ser realizados por usuário comum (sem privilégios de root).

Shell (root)
# Criar usuário para rodar processos de compilacao
    adduser -D builder;

# Adicionar no grupo de construtores (abuild)
    addgroup builder abuild;

# Dar poder de SUDO para rodar algumas operacoes como root
    echo "builder ALL=(ALL) NOPASSWD: ALL" > /etc/sudoers.d/builder;

1.3 – Alternar para modo usuário “builder”

De agora em diante vamos alterar para o modo usuário comum “builder“.

Você pode usar o “su” para obter shell (/bin/ash) como “builder” ou fazer logout do root e login nesse usuário.

Shell (root)
# Rodar o shell como usuario builder
# Shell ash
    su - builder -s /bin/ash;

# OU shell bash:
#   su - builder -s /bin/bash;

Agora logado como “builder“:

Shell ASH (user: builder)
# Conferir id:
# - Precisa ter uid diferente de zero
# - Precisa estar no grupo: abuild
    id;
    # uid=1000(builder) gid=1000(builder) groups=300(abuild),1000(builder)

# Entrar na pasta do usuário:
    cd ~;

# Conferir:
    pwd;
    # /home/builder

1.4 – Gerar configuração e chave privada

A chave privada (RSA 4096 bits) é necessária para assinar digitalmente os pacotes que você gerar.

Shell ASH (user: builder)
# Alternativa automatica (chave com caminho aleatorio):
#   echo | abuild-keygen -a -i;

# Variaveis
    # Contato responsavel
    NAME="Eu Mesmo";
    EMAIL="eu@mesmo.com";
    # Caminho das chaves
    PRIV="/home/builder/.abuild/builder-default.rsa";
    PUBK="$PRIV.pub"; 

# Criar pasta da config do abuild:
    mkdir -p .abuild;
    mkdir -p /home/builder/packages;

# Gerar chave privada RSA 4096 bits
    [ -s "$PRIV" ] || {
        openssl genrsa \
            -out         $PRIV \
            4096;
    };

# Gerar chave publica derivada da privada
    [ -s "$PUBK" ] || {
        openssl rsa \
            -in          $PRIV \
            -pubout -out $PUBK;
    };

# Gerar configuracao de usuario compilador
(
    echo "PACKAGER_PRIVKEY=\"$PRIV\"";
    echo "PACKAGER=\"$NAME <$EMAIL>\"";
    echo "MAINTAINER=\"$NAME <$EMAIL>\"";
    echo 'USE_COLORS=1';
    echo 'DABUILD_PACKAGES="/home/builder/packages"';
) > .abuild/abuild.conf;

# Colocar no caminho padrao:
sudo sh -c "cat .abuild/abuild.conf > /etc/abuild.conf";

# Copiar chave publica para a pasta de chaves do sistema:
sudo install -m 0644 -o root -g root "$PUBK" /etc/apk/keys/;
#sudo cp $PUBK /etc/apk/keys/;

O arquivo de configuração do abuilder será lido em /home/builder/.abuild/abuild.conf ou /etc/abuild.conf (caminho principal).

Diretórios para arquivos baixados (fontes, patchs, projetos git, etc):

Shell ASH (user: builder)
# Diretório de cache para arquivos baixados
sudo  mkdir -p      /var/cache/distfiles;
sudo  chgrp abuild  /var/cache/distfiles;
sudo  chmod g+w     /var/cache/distfiles;

1.5 – Obter os fontes oficiais

Os fontes do Alpine são conjuntos de pacotes “aports” contendo os scripts que baixam os fontes originais, patches (correções), dependências e ferramentas para construir um pacote final pronto para uso.

Shell ASH (user: builder)
# Baixando projeto aports do Alpine Linux
# - Na pasta do usuario
cd ~;

# - Clonar projeto git
git clone --depth 1 https://gitlab.alpinelinux.org/alpine/aports.git;

O projeto será baixado na pasta “aports“.

Diretórios em “./aports/“:

  • main/ – Pacotes oficiais que fazem parte da distribuição Alpine Linux, 1.653 pacotes;
  • community/: Pacotes de vários fabricantes e projetos diversos, 7.775 pacotes;
  • testing/: Pacotes em fase de teste para aprovação (branch edge), 3.167 pacotes;

Explore os diretórios para se acostumar com o formato.

2 – Analisando o pacote Redis

Vou usar o Redis como base para analise e recompilação.
Ele se encontra em: /home/builder/aports/community/redis

2.1 – Arquivos do pacote redis

Arquivos dentro do pacote Redis:

  • APKBUILD – Script shell POSIX, define variáveis e funções que serão executadas durante a construção do pacote.
    • pkgname: Nome do pacote;
    • pkgver: Versão;
    • pkgrel: Número incremental por compilação, reinicia ao mudar o pkgver;
    • pkgdesc: Descrição do pacote e projeto;
    • url: Url do projeto;
    • arch: Arquitetura, padrão “all”;
    • license: Nome da licença usada (ex: MIT);
    • depends: Nome dos pacotes necessários previamente para rodar o programa;
    • makedepends: Pacotes necessários para compilar o pacote;
    • checkdepends: Pacotes necessários para verificação do pacote;
    • install: Nome dos scripts usados na instalação do pacote final;
    • subpackages: Nome dos pacotes gerados pela compilação do projeto;
    • source: Arquivos e URLs onde os fontes, patchs e artefatos do projeto estão;
    • builddir: Diretório usado para trabalhar na construção do pacote;
    • build(): Função de construção;
    • check(): Função de verificação;
    • package(): Função de empacotamento;
  • Arquivos para o OpenRC:
    • redis-sentinel.initd – Instalado em /etc/init.d/redis-sentinel;
    • redis.initd – Instalado em /etc/init.d/redis;
    • redis.confd – Instalado em /etc/conf.d/redis;
  • Configurações final para uso do software:
    • redis.logrotate – Instalado em /etc/logrotate.d/redis;
  • Patchs e alterações do código original:
    • redis.conf.patch – Altera a configuração padrão do projeto Redis;
    • sentinel.conf.patch – Altera a configuração padrão do sentinel;
  • Scripts do pacote pronto para execução no ambiente de destino:
    • redis.pre-install – Script a executar antes da instalação;
    • redis.post-install – Script a executar após a instalação;

Agora vamos compilá-lo:

Shell ASH (user: builder)
# Entrar no projeto aports do Redis
cd ~/aports/community/redis/;

# Compilar:
# -r = instalar dependencias
# -c = saida colorida durante compilacao
abuild -r -c;

Verificar pacote pronto:

Shell ASH (user: builder)
# Verificar pacotes prontos:
find /home/builder/packages/community;

2.2 – Criando pacote personalizando do Redis

Vou copiar o pacote abuild do Redis e criar o “Redis-Native” que será compilado com instruções especiais (extensões da CPU local) para melhor performance.

Shell ASH (user: builder)
# Pasta do novo pacote
mkdir -p /home/builder/aports/community/redis-native;

# Copiar arquivos do pacote original
cp -rav \
    /home/builder/aports/community/redis/* \
    /home/builder/aports/community/redis-native;

# Entrar na nova pasta:
cd /home/builder/aports/community/redis-native;

# Renomear de 'redis' para 'redis-native'
sed -i '/^pkgname=redis$/s/redis/redis-native/'             APKBUILD;
sed -i '/redis.initd/s/redis/redis-native/'                 APKBUILD;
sed -i '/redis.confd/s/redis/redis-native/'                 APKBUILD;
sed -i '/redis-sentinel.initd/s/sentinel/native-sentinel/'  APKBUILD;
sed -i '/redis.logrotate/s/redis/redis-native/'             APKBUILD;

# Adicionar builddir para redis-$ver
sed -i '/pkgdesc/a builddir=$srcdir/redis-$pkgver'          APKBUILD;

# Renomear arquivos
mv  redis.pre-install     redis-native.pre-install;
mv  redis.post-install    redis-native.post-install;
mv  redis.initd           redis-native.initd;
mv  redis.confd           redis-native.confd;
mv  redis-sentinel.initd  redis-native-sentinel.initd;
mv  redis.logrotate       redis-native.logrotate;

Farei a alteração da função build() no arquivo APKBUILD para o seguinte código que optimizará para minha CPU:

Shell Script – APKBUILD (parcial, somente função build)
#...

# Original:
#- build() {
#-     export CFLAGS="$CFLAGS -DUSE_MALLOC_USABLE_SIZE -O2 -flto=auto"
#-     make USE_JEMALLOC=no MALLOC=libc BUILD_TLS=yes all
#- }

build() {
    # -mcpu=native: emite todo o ISA do CPU de build (LSE, PMULL, SHA2/3,
    #   FP16, FCMA, dot product, etc.) e já ativa o -mtune correspondente.
    # -O3: libera vetorização mais agressiva; Redis compila estável em -O3.
    # -flto=auto: LTO paralelo (já estava).
    # -fno-semantic-interposition: permite inline de símbolos internos.
    # -fno-plt: chamadas externas diretas, sem thunk da PLT.
    export CFLAGS="$CFLAGS -DUSE_MALLOC_USABLE_SIZE \
        -O3 \
        -flto=auto \
        -mcpu=native \
        -fno-semantic-interposition -fno-plt";

    export CXXFLAGS="$CFLAGS";

    export LDFLAGS="$LDFLAGS -flto=auto -Wl,-O1 -Wl,--as-needed";

    # Jemalloc bundled (default do Redis). Enorme ganho para carga real:
    # fragmentação menor e alocação multi-thread mais rápida.
    make \
        USE_JEMALLOC=yes \
        BUILD_TLS=yes \
        all;

    # Limpar binarios, remover tudo que nao é usado em producao
    binlist="
       redis-server
       redis-cli
       redis-sentinel
       redis-benchmark
       redis-check-aof
       redis-check-rdb
    ";
    for bin in $binlist; do
        strip -s \
            -R .comment \
            -R .note.gnu.build-id \
            -R .note.ABI-tag \
            -R .gnu.version \
            src/$bin;
    done;
}

#...

Construindo:

Shell ASH (user: builder)
# Compilar:
# -r = instalar dependencias
# -c = saida colorida durante compilacao
abuild -r -c;

Verificar pacote pronto:

Shell ASH (user: builder)
# Verificar pacotes prontos:
find /home/builder/packages/community;

x

3 – Construindo via Docker

O ideal é criar uma imagem de container que tenha todos os pacotes e preparativos prontos, assim você pula direto para a personalização e compilação do seu pacote.

.

“Comece fazendo o que é necessário,
depois o que é possível,
e de repente você estará
fazendo o impossível.”
São Francisco de Assis

Terminamos por hoje!

Patrick Brandão, patrickbrandao@gmail.com