sábado, 6 de abril de 2013

Script para restauração com o FSArchiver

Shell script que uso no SystemRescueCD para restaurar uma imagem do FSArchiver, contendo uma única partição (nada de swap) e apagando todos os dados do disco de destino. É para clonar distribuições Linux e não funcionará com Windows. Pressupõe máquinas com BIOS. Serve também para UEFI, mas será usado o CSM; ou seja, não será uma instalação "nativa" UEFI. Algumas distribuições usam caminho diferente para a configuração do GRUB. Idem com o nome de seus binários. Como está, funcionará com imagens do Fedora, CentOS (7+), Arch, openSUSE, Debian (8+) e Ubuntu (15.04+) ao menos.

Coloco-o num pendrive. Por mídia óptica deve funcionar também, basta montar um ISO personalizado com o genisoimage (veja no tópico do Clonezilla). Se a imagem do FSArchiver exceder 4 GiB, talvez seja preciso modificar o comando do genisoimage. De uma olhada na man page e na Wikipedia. Como referência, uma instalação do openSUSE 12.3 64-bit com KDE comprimida com LZMA (-z 7) pelo FSArchiver ficou com 1,38 GiB. De qualquer forma, aviso que não testei com mídia óptica. Deixo a tarefa para você.

Usei FAT32 no pendrive pela compatibilidade. FAT32 tem também o limite de 4 GiB por arquivo. Então, se necessário, formate com outro sistema de arquivos (dentre os suportados pelo GRUB4DOS), substituindo o comando do mkdosfs por outra ferramenta de acordo com o sistema de arquivos escolhido. É importante o rótulo ser especificado, pois o script depende dele para achar o pendrive — o mesmo vale para o genisoimage se você for usar mídia óptica.

Formatação do pendrive e seu bootloader
(considerando /dev/sdc — tudo será apagado!)

# parted -a cylinder -s /dev/sdc -- mklabel msdos mkpart primary fat32 0% 100% set 1 boot on
# mkdosfs -v -F 32 -n "SABUGODEMILHO" /dev/sdc1
# ./bootlace.com --no-backup-mbr --mbr-disable-floppy --time-out=0 /dev/sdc

GRUB4DOS aqui.

Arquivos do SystemRescueCd

Extrair do ISO do SystemRescueCd e colocar numa pasta chamada sysrcd (sem subpastas) dentro do pendrive os arquivos:

isolinux/{altker32,altker64,initram.igz,rescue32,rescue64}
sysrcd.dat
sysrcd.md5

Criar um arquivo de texto plano menu.lst na raiz do pendrive contendo:

color white/blue green/blue
default 0
timeout 20

title SystemRescueCd 64-bit
find --set-root /sysrcd/rescue64
kernel /sysrcd/rescue64 subdir=sysrcd setkmap=br-a docache nodmraid nomdadm nolvm lowmem
initrd /sysrcd/initram.igz

Apenas o kernel 64-bit e com a opção docache, para o pendrive poder ser removido sem preocupação depois do script terminar. Nem coloquei uma opção com o kernel 32-bit pois o chroot em sistemas 64-bit não funcionará. O kernel "altker64" é alternativa em caso de problemas.

Copiar o arquivo grldr do GRUB4DOS para a raiz do pendrive.

Criar um arquivo de texto (em UTF-8) chamado autorun dentro da pasta sysrcd com o seguinte conteúdo:
(ver Chapter 14: Run your own scripts at start-up with autorun)

#!/bin/bash

# Marcos FRM
# 25/11/2016

# Rótulo do pendrive
PENROTULO="SABUGODEMILHO"

# Nome do arquivo com a imagem do FSArchiver, presente na raiz do pendrive
ARQUIVOFSA="linux.fsa"

# Tempo, em segundos, antes de reiniciar, caso tudo ocorra bem
TEMPO=10

# --------------------------------------

unset PENMNT
unset DESTMNT
unset PENMNTOPTS
unset DESTMNTOPTS
unset MUDAUUID
unset MUDAFS
unset LISTAFS
unset FSNOVO
unset FSAOPT
PASSADAS=0

modprobe pcspkr 2>/dev/null

PENMNT=pen-$RANDOM
DESTMNT=dest-$RANDOM

function confere {
    local EXITCODE=$1
    local REMOVEMNT=$2
    local ERRMSG=$3
    local ERRCODE=$4

    if [[ $REMOVEMNT == 1 ]]; then
        if [[ $EXITCODE == 0 ]]; then
            rm -rf /mnt/{$PENMNT,$DESTMNT}
            echo
            echo "Pendrive pode ser removido."
            echo
            beep -l 500
            if [[ $ERRCODE ]]; then
                echo "$ERRMSG"
                echo
                exit $ERRCODE
            else
                echo "Reinicia em ${TEMPO}s..."
                echo
                sleep $TEMPO
                reboot
            fi
        else
            echo
            echo "$ERRMSG"
            echo
            beep -l 500
            exit $EXITCODE
        fi
    elif [[ $REMOVEMNT == 0 ]]; then
        if [[ $EXITCODE != 0 ]]; then
            echo
            echo "$ERRMSG"
            echo
            beep -l 500
            exit $EXITCODE
        fi
    fi
}

function calctam {
    local TAMANHO_B=$1
    if (( $TAMANHO_B > 0 )); then
        local TAMANHO_KB=$(($TAMANHO_B/1000))
        local TAMANHO_MB=$(($TAMANHO_KB/1000))
        local TAMANHO_GB=$(($TAMANHO_MB/1000))
        local TAMANHO_TB=$(($TAMANHO_GB/1000))
        if (( $TAMANHO_TB == 0 )); then
            if (( $TAMANHO_GB == 0 )); then
                printf '%3s MB\n' $TAMANHO_MB
            else
                printf '%3s GB\n' $TAMANHO_GB
            fi
        else
            printf '%3s TB\n' $TAMANHO_TB
        fi
    else
        echo "???   "
    fi
}

PENDEV=$(blkid -L $PENROTULO)

if [[ ! -b $PENDEV ]]; then
    echo
    echo "Pendrive não encontrado."
    echo
    exit 1
fi

if [[ $(uname -m) != x86_64 ]]; then
    echo
    echo "Rode num Linux 64-bit por favor."
    echo "chroot em sistemas x86-64 requer um kernel 64-bit."
    echo
    exit 1
fi

PENFSTYPE=$(blkid -p -s TYPE -o value $PENDEV)

if [[ $PENFSTYPE == vfat ]]; then
    PENMNTOPTS="-o utf8"
elif [[ $PENFSTYPE == ntfs ]]; then
    PENMNTOPTS="-t ntfs-3g"
fi

RESUMO="Pendrive ($PENDEV) formatado em \"$PENFSTYPE\""

if [[ $PENMNTOPTS ]]; then
    RESUMO+=", montado com \"$PENMNTOPTS\"."
else
    RESUMO+="."
fi

RESUMO+="\n\nPendrive: /mnt/$PENMNT\nDestino:  /mnt/$DESTMNT\n"

mkdir -p /mnt/{$PENMNT,$DESTMNT}
mount $PENDEV /mnt/$PENMNT $PENMNTOPTS
confere $? 0 "Falha ao montar pendrive."

echo -e "$RESUMO"

if [[ ! -r /mnt/$PENMNT/$ARQUIVOFSA ]]; then
    echo
    umount -v /mnt/$PENMNT
    confere $? 1 "Arquivo de imagem inexistente." 1
fi

if FSAINFO=$(LANG=C fsarchiver archinfo "/mnt/$PENMNT/$ARQUIVOFSA" 2>&1); then
    FSORIG=$(awk -F':[[:blank:]]*' '/^Filesystem format/ {print $2}' <<< "$FSAINFO")
    if (( $(awk -F':[[:blank:]]*' '/^Filesystems count/ {print $2}' <<< "$FSAINFO") > 1 )); then
        echo
        umount -v /mnt/$PENMNT
        confere $? 1 "Imagem contém mais de um sistema de arquivos. Cancelado." 1
    elif [[ ! $FSORIG =~ ext[2-4]|xfs|btrfs|jfs|reiserfs ]]; then
        echo
        umount -v /mnt/$PENMNT
        confere $? 1 "Imagem não contém um sistema de arquivos suportado (EXT2/3/4, XFS, Btrfs, JFS ou ReiserFS)." 1
    fi
else
    echo
    umount -v /mnt/$PENMNT
    confere $? 1 "Arquivo não é uma imgem do FSArchiver." 1
fi

# util-linux >= 2.22
for DISCO in $(lsblk -I 8 -drno NAME); do
    [[ $PENDEV =~ $DISCO ]] && continue
    MODELO=$(cat /sys/block/$DISCO/device/model)
    TAMANHO=$(calctam $(blockdev --getsize64 /dev/$DISCO))
    echo "/dev/$DISCO $TAMANHO - $MODELO"
    (( $PASSADAS == 0 )) && PRIMEIRODISCO=$DISCO
    ((PASSADAS++))
done

if (( $PASSADAS == 0 )); then
    umount -v /mnt/$PENMNT
    confere $? 1 "Nenhum dispositivo de armazenamento encontrado." 1
fi

echo
while true; do
    read -e -p "Dispositivo a ser restaurado: " -i "/dev/$PRIMEIRODISCO" DEV
    if [[ $DEV ]]; then
        if [[ ! -b $DEV ]]; then
            echo
            echo "Dispositivo inválido."
            echo
        elif [[ $PENDEV =~ ${DEV//[[:digit:]]/} ]]; then
            echo
            echo "Mesmo dispositivo do pendrive. Escolha outro."
            echo
        elif [[ $DEV =~ [[:digit:]]+$ ]]; then
            echo
            echo "Dispositivo inválido. Não especifique número de partição."
            echo
        else
            break
        fi
    else
        echo
    fi
done

echo
echo "Mudar sistema de arquivos? O atual é \"$FSORIG\"."
select RESP in SIM NÃO; do
    case $RESP in
        SIM)
            MUDAFS=1
            break
        ;;
        NÃO)
            break
        ;;
    esac
done

if [[ $MUDAFS ]]; then
    for FSATUAL in ext2 ext3 ext4 xfs btrfs jfs reiserfs; do
        [[ $FSATUAL == $FSORIG ]] && continue
        LISTAFS+="$FSATUAL "
    done
    echo
    echo "Escolha o novo sistema de arquivos."
    select RESP in $LISTAFS; do
        case $RESP in
            ext2)
                FSNOVO=ext2
                break
            ;;
            ext3)
                FSNOVO=ext3
                break
            ;;
            ext4)
                FSNOVO=ext4
                break
            ;;
            xfs)
                FSNOVO=xfs
                break
            ;;
            btrfs)
                FSNOVO=btrfs
                break
            ;;
            jfs)
                FSNOVO=jfs
                break
            ;;
            reiserfs)
                FSNOVO=reiserfs
                break
            ;;
        esac
    done
fi

echo
echo "Gerar novo UUID, machine-id e hostname?"
select RESP in SIM NÃO; do
    case $RESP in
        SIM)
            MUDAUUID=1
            break
        ;;
        NÃO)
            break
        ;;
    esac
done

echo
echo "Tem certeza? (TODOS os dados de $DEV serão apagados)"
select RESP in SIM NÃO; do
    case $RESP in
        SIM)
            break
        ;;
        NÃO)
            echo
            umount -v /mnt/$PENMNT
            confere $? 1 "Cancelado." 1
        ;;
    esac
done

echo
echo "Particionando..."
sgdisk -Z $DEV &>/dev/null
wipefs -aq $DEV
parted -s $DEV -- mklabel msdos mkpart primary ext4 0% 100% set 1 boot on
partprobe $DEV

echo
echo "Restaurando..."
UUIDORIG=$(awk -F':[[:blank:]]*' '/^Filesystem uuid/ {print $2}' <<< "$FSAINFO")
[[ $FSNOVO ]] && FSAOPT=",mkfs=$FSNOVO"
if [[ $MUDAUUID ]]; then
    UUIDNOVO=$(uuidgen)
    FSAOPT+=",uuid=$UUIDNOVO"
fi
wipefs -afq ${DEV}1
# fsarchiver >= 0.8.0
fsarchiver -j $(getconf _NPROCESSORS_ONLN) restfs "/mnt/$PENMNT/$ARQUIVOFSA" id=0,dest=${DEV}1$FSAOPT
RET=$?
if [[ $RET != 0 ]]; then
    echo
    umount -v /mnt/$PENMNT
    confere $? 1 "Erro na restauração." $RET
fi

echo
echo "Definindo configurações..."
# ReiserFS precisa de opções de montagem para habilitar ACLs e XATTRs
if [[ ( ! $FSNOVO && $FSORIG == reiserfs ) || $FSNOVO == reiserfs ]]; then
    DESTMNTOPTS='-o acl,user_xattr'
fi
mount ${DEV}1 /mnt/$DESTMNT $DESTMNTOPTS &>/dev/null
RET=$?
if [[ $RET != 0 ]]; then
    echo
    umount -v /mnt/$PENMNT
    confere $? 1 "Falha ao montar sistema alvo." $RET
fi
mount --bind /dev /mnt/$DESTMNT/dev
mount --bind /proc /mnt/$DESTMNT/proc
mount --bind /sys /mnt/$DESTMNT/sys

[[ -f /mnt/$DESTMNT/.readahead ]] && rm -f /mnt/$DESTMNT/.readahead
[[ -f /mnt/$DESTMNT/var/lib/ureadahead/pack ]] && rm -f /mnt/$DESTMNT/var/lib/ureadahead/pack
[[ -d /mnt/$DESTMNT/etc/NetworkManager/system-connections ]] && \
    rm -f /mnt/$DESTMNT/etc/NetworkManager/system-connections/*
if [[ -d /mnt/$DESTMNT/var/log/journal ]]; then
    find /mnt/$DESTMNT/var/log/journal -mindepth 1 -maxdepth 1 -type d -not -name 'remote' -exec rm -rf {} +
    [[ -d /mnt/$DESTMNT/var/log/journal/remote ]] && rm -rf /mnt/$DESTMNT/var/log/journal/remote/*
fi

if [[ $FSNOVO ]]; then
    sed -i "/$UUIDORIG/ s/$FSORIG/$FSNOVO/" /mnt/$DESTMNT/etc/fstab
    # ReiserFS: "acl,user_xattr", "user_xattr,acl", "acl", "user_xattr"
    # outros sistemas: "defaults"
    if [[ $FSORIG == reiserfs ]]; then
        sed -ri "/$UUIDORIG/ s/[[:blank:]]+(acl(,user_xattr)?|user_xattr(,acl)?)[[:blank:]]+/\tdefaults\t/" \
            /mnt/$DESTMNT/etc/fstab
    elif [[ $FSNOVO == reiserfs ]]; then
        sed -ri "/$UUIDORIG/ s/[[:blank:]]+defaults[[:blank:]]+/\tacl,user_xattr\t/" \
            /mnt/$DESTMNT/etc/fstab
    fi
fi

if [[ $MUDAUUID ]]; then
    sed -i "s/$UUIDORIG/$UUIDNOVO/" /mnt/$DESTMNT/etc/fstab
    [[ -e /mnt/$DESTMNT/var/lib/dbus/machine-id && ! -L /mnt/$DESTMNT/var/lib/dbus/machine-id ]] && \
        dbus-uuidgen >/mnt/$DESTMNT/var/lib/dbus/machine-id
    >/mnt/$DESTMNT/etc/machine-id
    chroot /mnt/$DESTMNT systemd-machine-id-setup
    rm -f /mnt/$DESTMNT/var/lib/systemd/random-seed
    echo "linux-$RANDOM" >/mnt/$DESTMNT/etc/hostname
fi

. /mnt/$DESTMNT/etc/os-release

# initramfs são regerados por último, depois das mudanças de UUID/machine-id/hostname terem sido aplicadas
case $ID in
    fedora|centos)
        find /mnt/$DESTMNT/etc/sysconfig/network-scripts -type f -name 'ifcfg-*' -a -not -name 'ifcfg-lo' -delete
        [[ -f /mnt/$DESTMNT/etc/NetworkManager/NetworkManager.conf ]] && \
            echo -e '[main]\nplugins=ifcfg-rh' >/mnt/$DESTMNT/etc/NetworkManager/NetworkManager.conf
        for RDLISTA in /mnt/$DESTMNT/boot/initramfs*; do
            # initramfs genérico (--no-hostonly), não precisa ser recriado
            [[ ${RDLISTA##*/} =~ rescue ]] && continue
            KVER=$(sed 's/^initramfs-//;s/\.img$//' <<< ${RDLISTA##*/})
            [[ -d /mnt/$DESTMNT/usr/lib/modules/${KVER:-xyz} ]] && \
                chroot /mnt/$DESTMNT dracut --verbose --force /boot/${RDLISTA##*/} $KVER
        done
    ;;
    opensuse)
        find /mnt/$DESTMNT/etc/sysconfig/network -type f -name 'ifcfg-*' -a -not -name 'ifcfg-lo' -delete
        [[ -f /mnt/$DESTMNT/etc/NetworkManager/NetworkManager.conf ]] && \
            echo -e '[main]\nplugins=ifcfg-suse,keyfile' >/mnt/$DESTMNT/etc/NetworkManager/NetworkManager.conf
        # no openSUSE 13.2+, mkinitrd é um shell script que chama o dracut com os parâmetros adequados
        chroot /mnt/$DESTMNT mkinitrd -B
    ;;
    debian)
        rm -f /mnt/$DESTMNT/etc/network/interfaces.d/*
        [[ -f /mnt/$DESTMNT/etc/NetworkManager/NetworkManager.conf ]] && \
            echo -e '[main]\nplugins=ifupdown,keyfile\n\n[ifupdown]\nmanaged=false' \
                >/mnt/$DESTMNT/etc/NetworkManager/NetworkManager.conf
        chroot /mnt/$DESTMNT update-initramfs -u -k all
    ;;
    ubuntu)
        rm -f /mnt/$DESTMNT/etc/network/interfaces.d/*
        [[ -f /mnt/$DESTMNT/etc/NetworkManager/NetworkManager.conf ]] && \
            echo -e '[main]\nplugins=ifupdown,keyfile,ofono\ndns=dnsmasq\n\n[ifupdown]\nmanaged=false' \
                >/mnt/$DESTMNT/etc/NetworkManager/NetworkManager.conf
        chroot /mnt/$DESTMNT update-initramfs -u -k all
    ;;
    arch)
        if [[ -f /mnt/$DESTMNT/etc/NetworkManager/NetworkManager.conf ]]; then
            # no Arch, nada de tralhas (resolvconf, netconfig)
            echo -e '[main]\nplugins=keyfile\nrc-manager=symlink' \
                >/mnt/$DESTMNT/etc/NetworkManager/NetworkManager.conf
            rm -f /mnt/$DESTMNT/etc/resolv.conf.bak
        fi
        chroot /mnt/$DESTMNT mkinitcpio -p linux
    ;;
esac

echo
echo "Instalando GRUB..."
if [[ $MUDAUUID ]]; then
    if [[ $ID == debian || $ID == ubuntu || $ID == arch ]]; then
        chroot /mnt/$DESTMNT grub-mkconfig -o /boot/grub/grub.cfg
    else
        chroot /mnt/$DESTMNT grub2-mkconfig -o /boot/grub2/grub.cfg
    fi
fi

if [[ $ID == debian || $ID == ubuntu || $ID == arch ]]; then
    chroot /mnt/$DESTMNT grub-install --recheck --no-floppy $DEV
else
    chroot /mnt/$DESTMNT grub2-install --recheck --no-floppy $DEV
fi

echo
echo "Desmontando tudo..."
# util-linux >= 2.23
umount -Rv /mnt/$DESTMNT /mnt/$PENMNT
confere $? 1 "Falha ao desmontar."

Personalize as linhas 7, 10 e 13.

O script está cheio de bashismos, logo, não altere a hashbang pois não sei se funcionará em outro shell.

Este script automatiza ideias da série de posts sobre o assunto:

Clonagem manual de partições com o Partclone
Clonagem manual de partições com o Partclone (II)
Clonagem manual de partições com o Partclone (III)
Clonagem manual de partições com o FSArchiver
Miúdos de clonagens manuais de partições
Miúdos de clonagens manuais de partições (II)

Changelog:

28/05/2013
- wipefs antes do parted

18/06/2013
- wipefs antes do fsarchiver de volta

21/06/2013
- http://caixaseca.blogspot.com.br/2013/06/miudos-de-clonagens-manuais-de.html

11/07/2013
- http://caixaseca.blogspot.com.br/2013/07/initramfs-hostonly-no-fedora-19.html

14/07/2013
- Melhorias diversas e melhor verificação de erros

11/08/2013
- Melhorias diversas e melhor verificação de erros
- sgdisk -Z antes do parted
  http://caixaseca.blogspot.com.br/2013/08/apagar-restos-do-particionamento-gpt.html

26/08/2013
- Reforma geral
  http://caixaseca.blogspot.com.br/2013/08/script-para-restauracao-com-o.html

11/03/2015
- Algumas simplificações e dois bugs corrigidos

03/05/2015
- Debian 8+

13/12/2015
- CentOS 7+
- Recria o initramfs depois de restaurar a imagem
  http://caixaseca.blogspot.com.br/2015/12/script-para-restauracao-com-o.html

14/12/2015
- Quando o sistema de arquivos for alterado, apenas trocar fs_vfstype da linha relativa ao seu UUID no fstab
- Algumas simplificações

15/12/2015
- Aplicar as mudanças de UUID/machine-id antes de regerar os initramfs
- Ao regerar o initramfs do Fedora/CentOS, conferir por garantia se o diretório dos módulos do kernel existe

03/07/2016
- Usar (( em comparações aritméticas
- grep em toda a saída de fsarchiver archinfo era uma pesquisa ampla demais, substituído por awk + linha começando com o campo requerido
- Verificar exit code do mount ao montar destino
- Emitir aviso se o sistema de arquivos for XFS e for requisitado novo UUID
- wipefs antes do parted (de novo)
  http://caixaseca.blogspot.com.br/2016/06/assinaturas.html
- Ubuntu 15.04+
- Remover pack do ureadahead

04/07/2016
- Versão do kernel estava errada no aviso sobre XFS

11/07/2016
- Melhorar aviso sobre XFS
- Não regerar /var/lib/dbus/machine-id se for um link simbólico para /etc/machine-id

13/08/2016
- FSArchiver 0.8.0:
  Regerar UUID de XFSv5 é livre de complicações graças à opção uuid=, aviso removido
  Desnecessário rodar ferramenta de cada sistema de arquivos após a restauração, todas removidas
  Adicionado suporte ao Btrfs, agora que seu UUID é preservado

19/08/2016
- Suporte ao Arch (requer UUID no fstab e GRUB)
- Manter o diretório /var/log/journal/remote (mas excluir seu conteúdo)

22/08/2016
- Montar ReiserFS com "-o acl,user_xattr"

25/08/2016
- Opções de montagem (fs_mntops) são ajustadas no fstab do sistema alvo: "defaults" é substituída por "acl,user_xattr" se o sistema de arquivos for trocado para ReiserFS; caso a troca seja de ReiserFS para outro, a substituição é invertida
  http://caixaseca.blogspot.com.br/2016/08/acls-e-xattrs-nos-principais-sistemas.html

01/09/2016
- Infelizmente, o NetworkManager do Arch não é compilado com
  --with-config-dns-rc-manager-default=symlink
  Adicionada, portanto, a opção rc-manager=symlink ao NetworkManager.conf

03/09/2016
- Alguns comentários adicionados

08/09/2016
- Usar, nas invocações de awk, [[:blank:]] e considerar a possibilidade de não haver espaçamento entre o separador e o segundo campo
- Substituir \s por [[:blank:]] ao mexer no fstab com sed: só estamos interessados em espaços e tabulações

09/09/2016
- Substituir invocações de grep por [[

28/09/2016
- Adicionar -f às opções do wipefs

30/09/2016
- wipefs no disco inteiro sem -f, assim o kernel é informado se houver mudança na tabela de partições (improvável depois de sgdisk -Z)

12/11/2016
- Gerar novo hostname ao mudar UUID/machine-id

14/11/2016
- Arch: dns=default é desnecessário em NetworkManager.conf

25/11/2016
- Remover /var/lib/systemd/random-seed ao gerar novo UUID/machine-id/hostname; ver discussão em:
  https://github.com/systemd/systemd/issues/4271
  https://github.com/systemd/systemd/pull/4513

Nenhum comentário:

Postar um comentário