Introdução
No post anterior, Automatizando a instalação do meu ambiente Arch Linux, falo sobre como automatizei a instalação do meu ambiente Arch Linux por meio de um script, que será detalhado neste artigo.
Portanto, o propósito deste post é fazer um breakdown desse script, cujo objetivo é automatizar toda a instalação do Arch com as configurações que costumo utilizar — desde o ambiente Live ISO até um sistema completamente configurado, com ou sem interface gráfica.
Para refrescar a memória sem precisar reler o post anterior, podemos resgatar o fluxo do script, já que ele é a parte mais importante para este artigo.
Live ISO (root)
└─ archinstall.sh
├─ sanity checks: root / internet
├─ user input: instalação minimalista ou com ambiente desktop
├─ user input: modo de boot BIOS ou UEFI
├─ detecta disco, fabricante da CPU, virtualização
├─ pergunta senhas + input: atualizar keyrings? + exibe o plano + confirmação
├─ particiona (parted) → formata → monta
├─ (opcional) atualiza o archlinux-keyring
├─ pacstrap: base + microcode + desktop + pacotes de virtualização
├─ genfstab
└─ arch-chroot /mnt → chroot-setup.sh
├─ fuso horário, locale, layout de teclado, hostname
├─ habilita o NetworkManager
├─ define senhas, cria usuário, configura o sudo
├─ grub-install + grub-mkconfig
├─ habilita LightDM/Cinnamon (se configurado)
└─ habilita serviços de VM guest (se detectado)
└─ desmonta → concluído, reinicie
Para não ser prolixo, já que o post anterior já aborda esses detalhes, vou resumir os requisitos para executar o script:
- ISO do Arch Linux bootada. De preferência a ISO atual disponibilizada no site oficial archlinux.org/download.
- Conexão com a internet
- Ter um meio de obter os arquivos do script na live ISO (via Git é a forma mais direta, clonando meu repositório,
git clone https://github.com/xyz-leo/dotfiles) - Conceder permissão de execução aos arquivos
archinstall.shechroot-setup.sh(chmod +x) - Executar o script, responder às perguntas e aguardar a instalação ser concluída.
Agora, vamos desmembrar o código do script e entender como ele funciona.
Breakdown
Link para o código-fonte do script: dotfiles/archinstall.
Shebang, set -euo, SCRIPT_DIR, source e logs
Essas são as primeiras linhas do script e são bem importantes:
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "$SCRIPT_DIR/config.sh"
log() { echo -e "\n==> $*"; }
err() { echo "ERROR: $*" >&2; }
Esses trechos de código representam um padrão comum em scripts Bash, então vale a pena entendê-los de verdade.
TL;DR:
#!/usr/bin/env bash→ diz ao sistema para executar o script com o Bash disponível no ambiente do usuário. É o shebang.set -euo pipefail→ ativa um modo seguro para o script em caso de falhas.-e→ encerra o script se um comando falhar.-u→ acusa erro ao usar uma variável não definida.-o pipefail→ faz uma pipeline falhar se qualquer comando dela falhar (por padrão, uma pipeline|retorna o código de saída apenas do último comando).
SCRIPT_DIR=...→ descobre o diretório onde o próprio script está, independentemente de onde ele foi executado.source "$SCRIPT_DIR/config.sh"→ importa outro arquivo Bash, permitindo reutilizar variáveis e funções.log()eerr()→ funções auxiliares para padronizar mensagens.err()envia a saída para ostderr, enquantolog()imprime mensagens comuns.
Agora, alguns dos detalhes importantes.
Primeiro, no geral, $ em Bash significa:
$1→ primeiro argumento do script/função.$2→ segundo argumento.$*→ todos os argumentos.$@→ todos os argumentos (preservando cada um separadamente).$#→ quantidade de argumentos.$?→ código de saída do último comando.$$→ PID do processo atual.$0→ nome do script.${...}→ forma mais explícita de expandir variáveis (${HOME}, ${nome}, etc.).- Também aparece na substituição de comandos, e.g.:
$(pwd)→ Aqui o$diz: "execute o comando entre parênteses e substitua isso pela saída dele".
Em resumo, o Bash expande expressões iniciadas por $, substituindo-as pelo valor correspondente antes de executar o comando.
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" → essa linha é composta por várias pequenas partes.
BASH_SOURCE→ É um array interno do Bash que contém o caminho dos scripts atualmente em execução;BASH_SOURCE[0]é o arquivo Bash atual.dirname→ Extrai apenas o diretório do caminho informado, e.g.:/root/dotfiles/archinstallcd→ Entra nesse diretório.pwd→ Obtém o caminho absoluto do diretório atual. Comocdnão produz saída,pwdé necessário para que$(...)capture esse caminho e o atribua à variávelSCRIPT_DIR.$(...)→ Executa o comando entre parênteses e substitui a expressão pela saída produzida.
err() { echo "ERROR: $*" >&2; } → função auxiliar para redirecionar uma mensagem para o stderr.
>&2→ redireciona a saída para ostderr(fluxo padrão destinado à saída de mensagens de erro de um processo).>→ redireciona a saída.&→ indica que o número seguinte representa um descritor de arquivo.2→ é o descritor de arquivostderr.
check_root e check_internet
Uma das primeiras coisas que o script faz é verificar se ele está sendo executado como root e se há conexão com a internet. Isso é necessário porque a instalação exige privilégios administrativos para executar diversos comandos e acesso à internet para baixar os pacotes.
check_root() {
[[ $EUID -eq 0 ]] || { err "Must run as root."; exit 1; }
}
check_internet() {
ping -c1 -W2 archlinux.org &>/dev/null || { err "No internet connection."; exit 1; }
}
check_root() → Verifica se o usuário que executou o script é root.
EUID→ É uma variável interna do Bash (Effective User ID) que contém o identificador do usuário efetivo do processo. O usuáriorootsempre possuiUID 0.[[ $EUID -eq 0 ]]→ Significa literalmente "o valor de$EUIDé igual a 0?" — i.e., verifica se o processo está rodando com permissões de root.|| { err...}→||significa literalmente "ou" — i.e., executa o bloco à direita caso a expressão à esquerda falhe. Nesse caso, exibe a mensagem de erro e encerra o processo com código de saída 1.
check_internet() → Verifica se o script possui conexão com a internet.
ping→ envia um pacote ICMP para testar comunicação com o host informado.-c1→ envia apenas 1 pacote.-W2→ espera no máximo 2 segundos por uma resposta.&>/dev/null→ descarta tanto a saída normal (stdout) quanto a saída de erro (stderr).
Para contextualização sobre comparação de operadores numéricos em Bash, os principais são:
-eq→ igual (equal)-ne→ diferente (not equal)-gt→ maior (greater)-ge→ maior ou igual (greater or equal)-lt→ menor (less than)-le→ menor ou igual (less or equal)
part_suffix e detect_disk
Essas duas funções estão relacionadas com as operações no disco.
part_suffix() {
local disk="$1" num="$2"
if [[ "$disk" =~ [0-9]$ ]]; then
echo "${disk}p${num}"
else
echo "${disk}${num}"
fi
}
detect_disk() {
[[ -n "$DISK" ]] && return
mapfile -t disks < <(lsblk -dnpo NAME,TYPE | awk '$2=="disk"{print $1}')
if [[ ${#disks[@]} -eq 0 ]]; then
err "No disks found."
exit 1
elif [[ ${#disks[@]} -eq 1 ]]; then
DISK="${disks[0]}"
else
echo "Multiple disks found:"
lsblk -dpo NAME,SIZE,MODEL
local i=1
for d in "${disks[@]}"; do
echo " $i) $d"
((i++))
done
read -rp "Select disk number: " choice
DISK="${disks[$((choice - 1))]}"
fi
}
part_suffix() → Responsável por montar uma string com o nome e a partição do disco corretos, e.g.: /dev/sda1 ou /dev/nvme0n1p1.
local disk="$1" num="$2"→ Em Bash, variáveis em funções são por padrão globais, o contrário da maioria das linguagens, então usamos local. Atribuímos os dois parâmetros recebidos pela função às variáveis locaisdiskenum.if [[ "$disk" =~ [0-9]$ ]]; then→ Usa um regex (=~ [0-9]$)para verificar se o nome do disco ($disk) termina com um dígito. Nesses casos, a nomenclatura das partições exige umpantes do número (e.g./dev/nvme0n1p1). Caso contrário, basta concatenar o número (e.g./dev/sda1)
detect_disk() → Detecta o disco que será utilizado durante a instalação e armazena seu caminho na variável global DISK.
Eu ainda não havia falado sobre as expressões condicionais [[ ]], ela basicamente avalia se a condição é verdadeira ou falsa.
[[ -n "$DISK" ]] && return→ A flag-nsignifica non-empty (não vazia). Portanto,returnserá executado apenas se a expressão à esquerda for verdadeira, ou seja, seDISKjá possuir algum valor. Nesse caso, significa que a variável já foi definida (emconfig.sh) e não é necessário detectar novamente o disco que será utilizado. Esse padrão é conhecido como guard clause, i.e., sair de uma função prematuramente quando uma condição específica é atendida.mapfile -t disks < <(lsblk -dnpo NAME,TYPE | awk '$2=="disk"{print $1}')→ Em resumo, essa linha combina uma sequência de ferramentas para ler várias linhas de texto e armazenar cada uma delas em um elemento de um array.- Exemplo: se a entrada for
/dev/sdae/dev/nvme0n1, após executarmapfile, o array serádisks[0]="/dev/sda"edisks[1]="/dev/nvme0n1". -t→ Flag que remove a quebra de linha (\n) do final de cada linha lida.lsblk -dnpo NAME,TYPE→ Lista dispositivos de bloco (HDs, SSDs, pendrives, NVMEs...). Cada letra representa uma flag:-dmostra apenas os discos (sem as partições);-nomite o cabeçalho;-pexibe o caminho completo do dispositivo;-odefine as colunas que serão exibidas (neste caso,NAMEeTYPE). A saída será algo como/dev/sda disk,/dev/nvme0n1 diske/dev/sda1 part.awk '$2=="disk"{print $1}'→ Quando a segunda coluna fordisk, imprime a primeira coluna. Na prática, filtra a saída dolsblk, mantendo apenas os discos e descartando partições, CD-ROMs e outros dispositivos.mapfile→ Lê as linhas produzidas pelo comando anterior e monta o arraydisks.
- Exemplo: se a entrada for
if,elifeelse→ Depois de preencher o arraydisks, o script verifica quantos discos foram encontrados.- Se o array
disksestiver vazio, encerra o script porque nenhum disco foi encontrado. - Se o array contiver apenas um elemento, utiliza esse disco automaticamente, já que não há outra opção.
- Caso contrário, significa que mais de um disco foi encontrado. O script exibe a lista de discos e solicita que o usuário escolha qual deles será utilizado.
- Se o array
read -rp "Select disk number: " choice→readlê uma entrada do usuário;-rfaz a leitura literalmente, sem interpretar barras invertidas (\);-pexibe um prompt antes de esperar a entrada (neste caso,"Select disk number: ");choiceé a variável onde a resposta será armazenada.DISK="${disks[$((choice - 1))]}"→ Atribui à variávelDISKo disco selecionado pelo usuário. O- 1existe porque a lista exibida ao usuário começa em 1, enquanto os índices de um array em Bash começam em 0.
swap_size_mib, detect_microcode e detect_virt
swap_size_mib() calcula o tamanho da partição swap que será criada posteriormente; detect_microcode() e detect_virt() detectam informações sobre a CPU e o ambiente de virtualização utilizando, respectivamente, /proc/cpuinfo e systemd-detect-virt.
swap_size_mib() {
local ram_mib
ram_mib=$(awk '/MemTotal/ {print int($2/1024)}' /proc/meminfo)
if (( ram_mib <= 2048 )); then
echo $(( ram_mib * 2 ))
elif (( ram_mib <= 8192 )); then
echo "$ram_mib"
else
echo 8192
fi
}
detect_microcode() {
local vendor
vendor=$(awk -F: '/vendor_id/{print $2; exit}' /proc/cpuinfo | tr -d ' ')
case "$vendor" in
GenuineIntel) echo "intel-ucode" ;;
AuthenticAMD) echo "amd-ucode" ;;
*) echo "" ;;
esac
}
detect_virt() {
# systemd-detect-virt prints "none" on bare metal too, but still exits
# non-zero in that case (its exit code is a separate is-virtualized
# boolean) -- naively `|| echo "none"` on that double-prints ("none\nnone").
local out
out=$(systemd-detect-virt 2>/dev/null) || true
echo "${out:-none}"
}
swap_size_mib() → A swap é uma área do disco utilizada como uma extensão da memória RAM. Como o disco é muito mais lento que a RAM, o sistema só recorre a ela quando realmente necessário. Essa função calcula o tamanho da partição swap com base na quantidade de memória RAM disponível. A lógica é simples: quanto menor a RAM de uma máquina, maior a chance de ela ser totalmente consumida durante picos de uso, fazendo com que a swap passe a ser utilizada para manter o sistema funcionando. Já em máquinas com mais memória (por exemplo, acima de 16 GiB), uma swap de 8 GiB costuma ser suficiente, pois é menos provável que toda a RAM seja utilizada.
ram_mib=$(awk '/MemTotal/ {print int($2/1024)}' /proc/meminfo)→ Novamente o programaawké utilizado para ler uma linha específica e aplicar uma ação. Nesse caso, ele lê o arquivo/proc/meminfo, procura pela linhaMemTotal, que contém a quantidade total de memória RAM da máquina, divide esse valor por1024(já queMemTotalé fornecido em kB) e retorna o resultado em MiB, atribuindo-o à variável localram_mib.if,elifeelse→ Verificam o valor deram_mibe, com base nele, definem o tamanho da partição swap em MiB.
detect_microcode() → Essa função detecta o fabricante da CPU para decidir qual pacote de microcode será instalado durante o pacstrap. Atualmente, apenas CPUs Intel e AMD são tratadas. Caso nenhum fabricante conhecido seja identificado, nenhum pacote adicional é instalado.
vendor=$(awk -F: '/vendor_id/{print $2; exit}' /proc/cpuinfo | tr -d ' ')→ Assim como emswap_size_mib(), oawké utilizado para ler um arquivo do sistema. Nesse caso, ele procura a linha que contémvendor_idem/proc/cpuinfo, extrai o fabricante da CPU e atribui o resultado à variável localvendor.-F:→ Define:como separador de colunas (Field Separator).exit→ Encerra oawkapós encontrar a primeira ocorrência devendor_id, já que basta ler uma única vez.| tr -d ' '→ O programatrremove todos os espaços da string retornada peloawk.
case "$vendor" in ...→ Verifica o valor devendor. Se forGenuineIntel, retorna o pacote de microcode da Intel; se forAuthenticAMD, retorna o da AMD; caso contrário, retorna uma string vazia.
detect_virt() → Detecta se o sistema está sendo executado em uma máquina virtual. Essa informação é utilizada posteriormente para instalar os pacotes e habilitar os serviços apropriados para o ambiente detectado.
out=$(systemd-detect-virt 2>/dev/null) || true→ Executa o programasystemd-detect-virt, que tenta identificar se o sistema está rodando em um ambiente virtualizado, e armazena a saída na variávelout.2>/dev/null→ Redireciona ostderrpara/dev/null, descartando mensagens de erro.|| true→ Sesystemd-detect-virtretornar erro (código diferente de0), executatrue, fazendo com que o script continue normalmente em vez de interromper a execução. > Observação: durante os testes,systemd-detect-virtidentificou meu notebook (hardware físico) como um ambiente virtualizado. Como essa função confia totalmente na saída desse programa, esse falso positivo faz com que o restante do script instale os pacotes e habilite os serviços correspondentes ao ambiente detectado. Na prática, isso não causa problemas relevantes, pois esses pacotes são pequenos, não interferem no funcionamento do sistema e apenas adicionam suporte para ambientes virtualizados.
Funções prompt, virt_packages_for, de_packages_for, update_keyring e confirm_plan
Repare que, até aqui, o script permaneceu em uma fase de reconhecimento: identificando o sistema, o ambiente em que está sendo executado, quem o executou, quais recursos estão disponíveis e tratando possíveis edge cases antes de realizar qualquer alteração. Somente depois dessa etapa é que a instalação propriamente dita começa.
Começaremos pelas funções prompt, que são as que solicitam input do usuário. Essas funções existem para coletar ou validar as últimas configurações necessárias antes da etapa destrutiva da instalação, permitindo tanto um modo interativo quanto um modo totalmente automatizado.
Funções prompt:
prompt_boot_mode() {
if [[ "$AUTO_CONFIRM" == true ]]; then
BOOT_MODE="uefi"
return
fi
# echo omitted
...
read -rp "Choice [0/1] (default: 1): " choice
choice="${choice:-1}"
case "$choice" in
0) BOOT_MODE="bios" ;;
1) BOOT_MODE="uefi" ;;
*) err "Invalid choice '$choice'."; exit 1 ;;
esac
if [[ "$BOOT_MODE" == "uefi" && ! -d /sys/firmware/efi/efivars ]]; then
log "Warning: this session doesn't look like it's booted in UEFI mode (/sys/firmware/efi/efivars missing) -- grub-install may fail later."
fi
}
prompt_install_type() {
[[ "$AUTO_CONFIRM" == true ]] && return
local default=1
[[ "$DESKTOP_ENV" == "none" ]] && default=0
# echo omitted
...
read -rp "Choice [0/1] (default: $default): " choice
choice="${choice:-$default}"
case "$choice" in
0) DESKTOP_ENV="none" ;;
1) DESKTOP_ENV="cinnamon" ;;
*) err "Invalid choice '$choice'."; exit 1 ;;
esac
}
prompt_update_keyring() {
UPDATE_KEYRING=false
[[ "$AUTO_CONFIRM" == true ]] && return
read -rp "Update keyrings before installing packages? Only needed if pacstrap fails on an old ISO. [y/N]: " ans
[[ "$ans" =~ ^[Yy] ]] && UPDATE_KEYRING=true
return 0
}
prompt_passwords() {
local pw1 pw2
read -rsp "Root password: " pw1; echo
read -rsp "Confirm root password: " pw2; echo
[[ "$pw1" == "$pw2" ]] || { err "Passwords do not match."; exit 1; }
ROOT_PASSWORD="$pw1"
read -rsp "Password for user $USERNAME: " pw1; echo
read -rsp "Confirm password for $USERNAME: " pw2; echo
[[ "$pw1" == "$pw2" ]] || { err "Passwords do not match."; exit 1; }
USER_PASSWORD="$pw1"
}
Dois padrões presentes no início das funções prompt a seguir são condições semelhantes a if [[ "$AUTO_CONFIRM" == true ]]; then e chamadas para read -rp. A primeira verifica se a variável AUTO_CONFIRM está habilitada; caso esteja, todas as perguntas interativas são ignoradas e a instalação utiliza os valores pré-configurados em config.sh. Já read -rp é utilizado para solicitar um input do usuário, armazenando a resposta em uma variável que será utilizada posteriormente pelo script.
Outro padrão presente nessas funções é o uso de case, utilizado para tratar diferentes possibilidades de entrada do usuário. A variável choice recebe esse input e, dependendo do valor informado, o script executa uma ação específica, como definir o modo de boot (BIOS ou UEFI) ou escolher o tipo de instalação (minimal ou desktop). Caso a entrada não corresponda a nenhuma opção esperada, o script trata como uma opção inválida.
virt_packages_for() e de_packages_for():
virt_packages_for() {
case "$1" in
oracle) echo "virtualbox-guest-utils" ;;
kvm|qemu) echo "qemu-guest-agent" ;;
*) echo "" ;;
esac
}
de_packages_for() {
case "$DESKTOP_ENV" in
cinnamon)
echo "xorg-server xorg-xinit lightdm lightdm-gtk-greeter cinnamon gnome-terminal gvfs gvfs-smb network-manager-applet xdg-user-dirs"
;;
none) echo "" ;;
*) err "Unknown DESKTOP_ENV: $DESKTOP_ENV"; exit 1 ;;
esac
}
Essas duas funções possuem uma lógica parecida: recebem uma configuração e retornam quais pacotes adicionais devem ser instalados durante o pacstrap.
virt_packages_for() → Responsável por retornar os pacotes necessários dependendo do ambiente virtualizado detectado.
case "$1" in→ O$1representa o primeiro argumento passado para a função. Nesse caso, ele recebe o valor deDETECTED_VIRT, que contém o tipo de virtualização detectada.oracle)→ Caso seja detectado VirtualBox, retorna o pacotevirtualbox-guest-utils.kvm|qemu)→ Caso seja detectado KVM ou QEMU, retorna o pacoteqemu-guest-agent.*)→ Caso nenhum dos casos anteriores seja encontrado, retorna uma string vazia, significando que nenhum pacote extra precisa ser instalado.
de_packages_for() → Possui a mesma ideia, mas relacionada ao ambiente gráfico escolhido pelo usuário.
case "$DESKTOP_ENV" in→ Verifica o valor da variávelDESKTOP_ENV, que define se a instalação terá um ambiente desktop ou será minimalista.cinnamon)→ Retorna a lista de pacotes necessários para instalar o ambiente Cinnamon, incluindo servidor gráfico, gerenciador de login e alguns utilitários.none)→ Retorna uma string vazia, pois uma instalação sem ambiente gráfico não precisa desses pacotes.*)→ Caso o valor deDESKTOP_ENVnão seja reconhecido, exibe um erro e encerra o script.
Em resumo, essas funções apenas fazem uma ponte entre uma configuração detectada/escolhida e os pacotes que precisam ser instalados posteriormente pelo pacstrap.
update_keyring() e confirm_plan():
update_keyring() {
log "Updating archlinux-keyring"
pacman -Sy --noconfirm archlinux-keyring
}
confirm_plan() {
echo
echo "About to install Arch Linux with the following plan:"
echo " Disk: $DISK (ALL DATA WILL BE ERASED)"
echo " Boot mode: $BOOT_MODE"
if [[ "$BOOT_MODE" == "uefi" ]]; then
echo " EFI: ${EFI_SIZE_MIB}MiB"
fi
echo " Swap mode: $SWAP_MODE"
echo " Root fs: $ROOT_FS"
echo " Hostname: $HOSTNAME"
echo " User: $USERNAME ($USER_GROUPS)"
echo " Locale: $LOCALE_LANG (regional: $LOCALE_REGIONAL)"
echo " Timezone: $TIMEZONE"
echo " Keymap: $KEYMAP_CONSOLE (X11: $KEYMAP_X11_LAYOUT/$KEYMAP_X11_MODEL)"
echo " Desktop: $DESKTOP_ENV"
echo " Keyring: $([[ "$UPDATE_KEYRING" == true ]] && echo "update before install" || echo "skip")"
echo " Virt: $DETECTED_VIRT"
echo
[[ "$AUTO_CONFIRM" == true ]] && return
read -rp "Type 'yes' to continue: " ans
[[ "$ans" == "yes" ]] || { echo "Aborted."; exit 1; }
}
update_keyring() → Atualiza o keyring do Arch Linux. Observação: a função update_keyring() só é chamada posteriormente no script.
pacman -Sy --noconfirm archlinux-keyring→ Utiliza o gerenciador de pacotes do Arch Linux (pacman, de package manager, não o jogo, haha) para atualizar o pacotearchlinux-keyring.Sy→-S(Sync) instala pacotes a partir dos repositórios;-y(Refresh) atualiza a lista de pacotes disponíveis.--noconfirm→ Não solicita confirmação do usuário, permitindo que o script execute a atualização de forma automática.
confirm_plan() → A maior parte da função consiste em chamadas para echo, utilizadas para exibir ao usuário um resumo da instalação antes que qualquer alteração seja feita no sistema. São mostradas informações como o disco que será formatado, modo de boot, sistema de arquivos, swap, hostname, usuário, layout do teclado, ambiente gráfico e outras configurações relevantes.
read -rp "Type 'yes' to continue: " ans→ Lê o input do usuário e armazena na variávelans(answer).[[ "$ans" == "yes" ]] || { echo "Aborted."; exit 1; }→ Verifica se o valor deansé "yes", a única resposta aceita para prosseguir com a instalação. Caso contrário, encerra o script.
deactivate_disk, partition_disk, format_partitions e mount_partitions
A partir daqui as funções realizam alterações no disco que será utilizado para realizar a instalação.
deactivate_disk() {
umount -R /mnt 2>/dev/null || true
local dev
while read -r dev; do
[[ "$dev" == "$DISK"* ]] && swapoff "$dev" 2>/dev/null || true
done < <(swapon --show=NAME --noheadings 2>/dev/null)
return 0
}
partition_disk() {
log "Partitioning $DISK"
deactivate_disk
wipefs -a "$DISK"
local swap_mib
if [[ "$SWAP_MODE" == "auto" ]]; then
swap_mib=$(swap_size_mib)
else
swap_mib="$SWAP_SIZE_MIB"
fi
if [[ "$BOOT_MODE" == "uefi" ]]; then
local efi_end=$(( 1 + EFI_SIZE_MIB ))
local swap_end=$(( efi_end + swap_mib ))
parted --script "$DISK" \
mklabel gpt \
mkpart ESP fat32 1MiB "${efi_end}MiB" \
set 1 esp on \
mkpart primary linux-swap "${efi_end}MiB" "${swap_end}MiB" \
mkpart primary ext4 "${swap_end}MiB" 100%
partprobe "$DISK"
udevadm settle
EFI_PART=$(part_suffix "$DISK" 1)
SWAP_PART=$(part_suffix "$DISK" 2)
ROOT_PART=$(part_suffix "$DISK" 3)
else
local swap_end=$(( 1 + swap_mib ))
parted --script "$DISK" \
mklabel msdos \
mkpart primary linux-swap 1MiB "${swap_end}MiB" \
mkpart primary ext4 "${swap_end}MiB" 100% \
set 2 boot on
partprobe "$DISK"
udevadm settle
SWAP_PART=$(part_suffix "$DISK" 1)
ROOT_PART=$(part_suffix "$DISK" 2)
fi
}
format_partitions() {
log "Formatting partitions"
[[ "$BOOT_MODE" == "uefi" ]] && mkfs.fat -F32 "$EFI_PART"
mkswap "$SWAP_PART"
swapon "$SWAP_PART"
"mkfs.${ROOT_FS}" -F "$ROOT_PART"
}
mount_partitions() {
log "Mounting partitions"
mount "$ROOT_PART" /mnt
if [[ "$BOOT_MODE" == "uefi" ]]; then
mount --mkdir "$EFI_PART" /mnt/boot
fi
}
deactivate_disk() → Essa função desmonta recursivamente tudo dentro de /mnt e desativa qualquer partição swap ativa que pertença ao disco que será utilizado na instalação. Ela existe como uma etapa de limpeza antes de particionar o disco novamente.
É interessante notar que essa função não fazia parte da primeira versão do script. Ela surgiu durante os testes, quando algumas tentativas de instalação falhavam porque o disco continuava sendo considerado "ocupado". Isso acontecia porque execuções anteriores deixavam estados pendentes, como partições montadas ou swap ainda ativa, impedindo que as operações seguintes fossem realizadas corretamente.
umount -R /mnt→ Desmonta/mntrecursivamente.2>/dev/nulle|| true→ Joga a saída para o "vazio" e continua o script mesmo o comando se falhar.
while read -r dev; do ... done < <(swapon --show=NAME --noheadings 2>/dev/null)→ Percorre todos os dispositivos que estão sendo utilizados como swap no sistema.swapon --show=NAME --noheadings→ Lista as partições swap ativas, mostrando apenas o nome do dispositivo e removendo o cabeçalho, e.g.:/dev/nvme0n1p2.< <(...)→ É chamado de process substitution. Ele pega a saída do comando dentro dos parênteses e transforma em um input para owhile. Na prática, cada linha retornada peloswaponserá lida pelo loop.while read -r dev→ A cada iteração, uma linha de output é armazenada na variáveldev.
[[ "$dev" == "$DISK"* ]] && swapoff "$dev" 2>/dev/null || true→ Verifica se a partição swap pertence ao disco que será formatado. O*significa "qualquer coisa depois", então seDISK="/dev/nvme0n1", uma partição como/dev/nvme0n1p2será considerada pertencente a esse disco.- Se a condição for verdadeira, executa
swapoff "$dev", desativando aquela partição swap antes da formatação. 2>/dev/nulldescarta mensagens de erro caso a desativação falhe.|| truegarante que o script continue mesmo se essa operação falhar, algo importante por causa doset -eativado no início do script.
- Se a condição for verdadeira, executa
Em resumo, essa parte existe para garantir que o disco não esteja em uso antes de ser apagado e particionado novamente. Caso uma instalação anterior tenha deixado uma swap ativa, ela é desativada automaticamente.
partition_disk() → Limpa o disco completamente e cria a estrutura de partições que será utilizada pela instalação.
Aqui existe um ponto interessante: eu não enxergava claramente a diferença entre particionar um disco e formatar suas partições, mas são etapas separadas. Particionar pode ser entendido como "dividir o disco em partes organizadas", enquanto formatar uma partição é a etapa que cria um sistema de arquivos dentro dela, permitindo que ela armazene dados.
deactivate_disk()→ Chama a função discutida anteriormente para garantir que o disco não esteja em uso, evitando estados pendentes de instalações anteriores que poderiam impedir as operações de particionamento.wipefs -a "$DISK"→ Remove assinaturas antigas de sistemas de arquivos, como ext4, NTFS e swap. Isso é necessário para evitar que o Linux encontre "restos" de instalações antigas e interprete o disco de forma incorreta.if [[ "$SWAP_MODE" == "auto" ]]; then→ Verifica se a variávelSWAP_MODE(definida emconfig.sh) está configurada comoauto. Nesse caso, chama a funçãoswap_size_mib()para calcular automaticamente o tamanho da swap; caso contrário, utiliza o valor definido emSWAP_SIZE_MIB.if [[ "$BOOT_MODE" == "uefi" ]]; then→ Verifica se o modo de boot escolhido é UEFI. Caso seja, cria as partições seguindo o esquema necessário para esse tipo de boot.parted --script "$DISK"→partedé o programa utilizado para manipular a tabela de partições do disco. O parâmetro--scriptfaz com que ele execute os comandos automaticamente, sem entrar no modo interativo ou pedir confirmações. Os comandos seguintes são instruções passadas para oparted.mklabel gpt→ Cria uma nova tabela de partições no formato GPT (GUID Partition Table). Esse é o padrão utilizado em instalações UEFI modernas.mkpart ESP fat32 1MiB "${efi_end}MiB"→ Cria uma partição chamadaESP(EFI System Partition), reservada para armazenar os arquivos de inicialização do UEFI. Ela utiliza o formato FAT32 e tem seu início e fim definidos pelos valores informados.set 1 esp on→ Marca a primeira partição como uma ESP. Essa flag informa ao firmware UEFI que aquela partição contém arquivos de boot.mkpart primary linux-swap "${efi_end}MiB" "${swap_end}MiB"→ Cria a partição destinada à swap. Nesse momento, ela apenas define a área do disco; a criação da swap propriamente acontece depois commkswap.mkpart primary ext4 "${swap_end}MiB" 100%→ Cria a partição raiz do sistema, utilizando todo o espaço restante do disco. Oext4indica o tipo esperado da partição, mas o sistema de arquivos é criado posteriormente commkfs.ext4.
partprobe "$DISK"→ O programapartprobeinforma ao kernel que a tabela de partições do disco foi alterada. Pense nele como um "refresh" para que o sistema reconheça a nova estrutura sem precisar reiniciar.udevadm settle→ Aguarda oudevfinalizar a criação dos dispositivos correspondentes às novas partições. Isso evita que o script tente acessar uma partição antes que ela exista em/dev(race condition).else→ Executa uma lógica semelhante, porém criando as partições seguindo o esquema tradicional de BIOS/MBR, que utiliza uma tabela de partiçõesmsdosem vez de GPT e não necessita de uma partição EFI separada.
format_partitions() → Como a função anterior já criou as partições, agora o próximo passo é formatá-las, ou seja, criar os sistemas de arquivos que serão utilizados pelo sistema operacional.
[[ "$BOOT_MODE" == "uefi" ]] && mkfs.fat -F32 "$EFI_PART"→ SeBOOT_MODEforUEFI, formata a partiçãoEFI_PARTutilizando o sistema de arquivosFAT32.mkswap "$SWAP_PART"eswapon "$SWAP_PART"→ O primeiro comando configura a partiçãoSWAP_PARTcomo uma área de swap; o segundo ativa essa área para que ela possa ser utilizada pelo sistema."mkfs.${ROOT_FS}" -F "$ROOT_PART"→ Formata a partiçãoROOT_PARTutilizando o sistema de arquivos definido emconfig.sh. Atualmente, esse valor éext4, mas o comando é montado dinamicamente a partir da variávelROOT_FS, permitindo trocar o sistema de arquivos sem alterar o script.
mount_partitions() → mount é o comando responsável por tornar um sistema de arquivos acessível ao Linux através de um diretório. Durante a instalação do Arch, por convenção utiliza-se /mnt como ponto de montagem da nova instalação. Assim, tudo que for instalado em /mnt será, na realidade, gravado na partição root recém-criada.
mount "$ROOT_PART" /mnt→ Monta a partição raiz em/mnt.mount --mkdir "$EFI_PART" /mnt/boot→ Executado apenas em instalaçõesUEFI. Cria o diretório/mnt/bootcaso ele ainda não exista e monta nele a partiçãoESP, permitindo que os arquivos de boot sejam gravados no local correto.
pacstrap_system e genfstab_system
Agora que tudo foi preparado, é o momento de instalar o Arch Linux!
pacstrap_system() {
log "Installing base system (pacstrap)"
local microcode virt_pkgs de_pkgs
microcode=$(detect_microcode)
virt_pkgs=$(virt_packages_for "$DETECTED_VIRT")
de_pkgs=$(de_packages_for)
local pkgs=(base linux linux-firmware networkmanager grub sudo vim nano git base-devel)
[[ "$BOOT_MODE" == "uefi" ]] && pkgs+=(efibootmgr)
[[ -n "$microcode" ]] && pkgs+=("$microcode")
[[ -n "$virt_pkgs" ]] && pkgs+=($virt_pkgs)
[[ -n "$de_pkgs" ]] && pkgs+=($de_pkgs)
pacstrap -K /mnt "${pkgs[@]}"
}
genfstab_system() {
log "Generating fstab"
genfstab -U /mnt >> /mnt/etc/fstab
}
O pacstrap responde à seguinte pergunta:
Como colocar um Arch Linux inteiro dentro dessa partição?
Ele baixa os pacotes dos repositórios do Arch Linux e os instala no diretório informado (por convenção, durante a instalação, utiliza-se /mnt). O motivo de não utilizar diretamente o pacman é que ele instala pacotes no sistema atualmente em execução, que neste caso é a Live ISO. Já o pacstrap instala os pacotes em outro diretório. Vale destacar que o pacstrap é um wrapper do pacman: ele utiliza o pacman internamente para baixar e instalar os pacotes, mas adiciona uma lógica extra para permitir a instalação de um sistema completo em um diretório diferente do sistema em execução.
pacstrap_system() → Essa função monta um array com todos os pacotes necessários para a instalação e, ao final, utiliza o pacstrap para instalar esse conjunto em /mnt, que nesse momento representa a partição raiz (root) do sistema que está sendo construindo.
microcode=$(detect_microcode),virt_pkgs=$(virt_packages_for "$DETECTED_VIRT")ede_pkgs=$(de_packages_for)→ Chamam as funções criadas anteriormente para descobrir quais pacotes adicionais devem ser instalados e armazenam seus retornos nas variáveismicrocode,virt_pkgsede_pkgs.local pkgs=(base linux linux-firmware networkmanager grub sudo vim nano git base-devel)→ Inicializa o arraypkgscom os pacotes que sempre serão instalados, independentemente do tipo de instalação. Eles representam a base de um sistema Arch Linux funcional.[[ "$BOOT_MODE" == "uefi" ]] && pkgs+=(efibootmgr)→ Se o modo de boot forUEFI, adiciona o pacoteefibootmgrao arraypkgs.[[ -n "$..." ]] && pkgs+=("$...")→ A mesma lógica é utilizada para adicionar ao array os pacotes de microcode, virtualização e ambiente gráfico, caso existam.pacstrap -K /mnt "${pkgs[@]}"→ Finalmente, instala o sistema Arch Linux e todos os pacotes reunidos no arraypkgsdentro de/mnt, ou seja, na partição raiz recém-criada.
genfstab_system() → Essa função gera automaticamente o arquivo fstab (File System Table), responsável por informar ao Linux quais partições devem ser montadas durante a inicialização do sistema e em quais diretórios elas devem aparecer.
genfstab -U /mnt >> /mnt/etc/fstab→ Ogenfstabanalisa tudo o que está montado dentro de/mnte gera as entradas correspondentes para o arquivofstab. A flag-Ufaz com que as partições sejam identificadas peloUUID(Universally Unique Identifier), um identificador único de cada sistema de arquivos, em vez de caminhos como/dev/sda1, que podem mudar. Por fim,>>redireciona essa saída para o arquivo/mnt/etc/fstab, que será utilizado pelo sistema após a instalação.
prepare_chroot, run_chroot e cleanup_chroot_files
Agora, o Arch está instalado na máquina, o objetivo é configurar o sistema antes de reiniciá-lo. Nesta etapa, entra o arch-chroot, que é um mecanismo do Linux que muda temporariamente qual o diretório que será considerado a raiz / de um processo. Isso será útil para fazermos todas as configurações que precisamos.
prepare_chroot() {
log "Preparing chroot setup"
cp "$SCRIPT_DIR/config.sh" /mnt/root/config.sh
cp "$SCRIPT_DIR/chroot-setup.sh" /mnt/root/chroot-setup.sh
chmod +x /mnt/root/chroot-setup.sh
install -m 600 /dev/null /mnt/root/.archinstall_secrets
cat > /mnt/root/.archinstall_secrets <<EOF
ROOT_PASSWORD='${ROOT_PASSWORD}'
USER_PASSWORD='${USER_PASSWORD}'
DETECTED_VIRT='${DETECTED_VIRT}'
BOOT_MODE='${BOOT_MODE}'
DISK='${DISK}'
DESKTOP_ENV='${DESKTOP_ENV}'
EOF
}
run_chroot() {
log "Entering chroot for system configuration"
arch-chroot /mnt /root/chroot-setup.sh
}
cleanup_chroot_files() {
rm -f /mnt/root/.archinstall_secrets /mnt/root/config.sh /mnt/root/chroot-setup.sh
}
prepare_chroot() → Prepara o ambiente chroot, copiando os arquivos necessários para a continuidade da configuração do sistema.
cp "$SCRIPT_DIR/config.sh" /mnt/root/config.shecp "$SCRIPT_DIR/chroot-setup.sh" /mnt/root/chroot-setup.sh→ Copia os arquivosconfig.shechroot-setup.shpara/mnt/root/, permitindo que eles sejam executados após entrar no ambientechroot.install -m 600 /dev/null /mnt/root/.archinstall_secrets→ Cria o arquivo.archinstall_secretscom permissões restritas (600), permitindo leitura e escrita apenas pelo usuárioroot.cat > /mnt/root/.archinstall_secrets <<EOF ... EOF→ Escreve nesse arquivo informações necessárias para a próxima etapa da instalação, como senhas, modo de boot, tipo de instalação e demais variáveis que serão utilizadas pelochroot-setup.sh.
run_chroot() → Entra no ambiente chroot e executa o script responsável pela configuração do sistema recém-instalado.
arch-chroot /mnt /root/chroot-setup.sh→ Entra no ambientechroot, fazendo com que/mntpasse a ser tratado como a raiz (/) do sistema de arquivos, e executa o scriptchroot-setup.sh.
cleanup_chroot_files() → Remove do sistema os arquivos utilizados apenas durante a instalação, incluindo o .archinstall_secrets, que contém informações sensíveis, como as senhas.
rm -f /mnt/root/.archinstall_secrets /mnt/root/config.sh /mnt/root/chroot-setup.sh→ Remove os arquivos temporários utilizados durante a instalação. A flag-f(force) evita erros caso algum deles já não exista.
main
A função main funciona como um orquestrador. Ela praticamente não contém lógica própria; sua responsabilidade é apenas chamar as funções na ordem correta, organizando todo o fluxo da instalação.
Esse padrão torna o script mais legível e facilita a manutenção, já que cada função possui uma única responsabilidade, enquanto a main apenas descreve a sequência das etapas.
main() {
check_root
check_internet
prompt_install_type
prompt_boot_mode
detect_disk
DETECTED_VIRT=$(detect_virt)
prompt_passwords
prompt_update_keyring
confirm_plan
partition_disk
format_partitions
mount_partitions
[[ "$UPDATE_KEYRING" == true ]] && update_keyring
pacstrap_system
genfstab_system
prepare_chroot
trap cleanup_chroot_files EXIT
run_chroot
log "Installation finished. Unmounting."
umount -R /mnt
swapoff "$SWAP_PART" || true
echo
echo "All done. Run 'reboot' when ready."
}
main "$@"
É possível dividir seu fluxo em quatro grandes fases:
- Validação e coleta de informações → Verifica permissões, conexão com a internet, coleta informações sobre o sistema e solicita os dados necessários ao usuário.
- Preparação do disco → Particiona, formata e monta as partições que receberão o novo sistema.
- Instalação do sistema base → Instala o Arch Linux com
pacstrap, gera ofstabe prepara o ambiente para entrar nochroot. - Configuração final → Executa o script
chroot-setup.sh, desmonta as partições, desativa a swap e encerra a instalação.
Alguns detalhes merecem destaque:
[[ "$UPDATE_KEYRING" == true ]] && update_keyring→ Atualiza oarchlinux-keyringapenas se o usuário solicitou essa etapa.trap cleanup_chroot_files EXIT→ Registra a funçãocleanup_chroot_filespara ser executada automaticamente quando o script terminar, independentemente do motivo (fim da execução ou erro). Isso garante que arquivos temporários, como.archinstall_secrets, não permaneçam no sistema após a instalação.main "$@"→ Inicia a execução do script chamando a funçãomain. O"$@"representa todos os argumentos passados pela linha de comando e os encaminha para a função, embora neste script eles não sejam utilizados.
Finalmente acabou?
Com certeza este foi um post enorme, cheio de detalhes e cansativo tanto de escrever quanto de ler. Ainda assim, espero que ele tenha mostrado que a instalação de um sistema operacional envolve muito mais do que parece; também que quando quebramos o processo em pequenas etapas, cada uma passa a fazer bastante sentido.
Entretanto, ainda não acabou. No próximo post, veremos o que acontece durante a configuração do sistema dentro do chroot, antes da instalação ser concluída. Embora faça parte do mesmo processo, essa etapa merece um artigo próprio, pois possui responsabilidades bem diferentes do archinstall.sh. Felizmente, ele será consideravelmente menor e menos denso do que este.