Creare uno stage4 in Linux

Intanto, cos’è uno stage4?

stage4 è un termine che deriva dalle prime installazioni di Gentoo Linux che mettevano a disposizione tre diverse modalità di installazione con diversi gradi rispetto a ciò che veniva fornito precompilato.

In sostanza, come già detto in questo articolo, per installare Gentoo si esegue il download di un unico file in formato tar.bz che, una volta decompresso, crea un sistema base sul quale è possibile iniziare a lavorare. Oramai si utilizza sempre lo “stage3” che, in un download di circa 150 MB, include tutti gli strumenti utili per la compilazione e il sistema di base senza il kernel.

Inizialmente, però, gentoo prevedeva anche lo stage1 e lo stage2:

  • lo stage1 è sostanzialmente il download di un file con solo gli strumenti per poter compliare il compilatore C

  • lo stage2 è il download di un file con tutti gli strumenti necessari per compilare il sistema di base

  • lo stage3, come già detto, è il download di un file contenete dia gli strumenti per compilare (stage2) con già il sistema di base precompilato.

Lo stage4 è quindi lo step successivo, cioè il file contenente il backup completo di una macchina linux compilata e operativa: vediamolo un po’ come il “disaster recovery” di un server Linux.

Questa operazione è abbastanza conosciuta su sistemi Gentoo Linux, però è comunqe adattabile a qualsiasi distribuzione in quanto utilizza strumenti standard. La cosa interessante è che, per creare uno stage4, non è necessario spegnere la macchina, ma è possibile farlo mentre la stessa è operativa.

Per creare lo stage4 di una macchina esistono numerosi script, tutti scaricabili a questo indirizzo.

Personalmente quello che preferisco è questo:

<strong>mkstage4.sh</strong>
#!/bin/bash

# Backup script for Gentoo Linux
#
# mkstage4.sh is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# mkstage4.sh is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# Copyright: Reto Glauser aka blinkeye
# Mailto: stage4 at blinkeye dot ch
# Homepage: http://blinkeye.ch
# Forum post: http://forums.gentoo.org/viewtopic-t-312817.html
# Date: 2009-04-02

version=v3.7
basename=`basename $0`

find=/usr/bin/find
tar=/bin/tar

# these are the commands we actually need for the backup
command_list=(cut date echo $find grep hostname mount sh split $tar umount uname which)

# verify that each command we use exists. if one can't be found use $PATH and make a suggestion if possible.
for command in ${command_list[@]}; do
        if [ ! -x "`which $command 2>&1`" ]; then
                echo -e "\nERROR: $command not found! "
                base=`basename $command`
                if [ "`which $base 2>&1 | grep "no \`basename $command\` in"`" != "" ]; then
                        echo -e "ERROR: $base is not in your \$PATH."
                fi
                exit -1
        fi
done

help="\nUsage:\n\nsh `basename $0` [[-v]|[--verbose]] [[-s]|[--split]] \n\nTo run the script NOT in verbose mode comes in handy if you want to see only the errors that occur during the backup.\n"

# Defaults to creating one tarball
tar_output="--file"

# split command
split_options="--suffix-length=1 --bytes=685m"

# options for the tar command
tarOptions=" --preserve-permissions --create --absolute-names --totals --ignore-failed-read"

# where to put the stage4
stage4Location=/mnt/backups/stage4

# name prefix
stage4prefix=`hostname`-stage4-`date +\%Y.\%m.\%d`

# patterns which should not be backed up (like iso files).
# example: default_exclude_pattern="*.iso *.divx"
# These pattern count only for files NOT listed in the $custom_include_list.
default_exclude_pattern=""

# these files/directories are always excluded. don't add trailing slashes.
# don't touch it unless you know what you are doing!
# /var/db and /var/cache/edb are intentionally added here. they are listed
# in $default_include_folders
default_exclude_list="
/dev
/lost+found
/mnt
/proc
/sys
/tmp
/usr/portage
/usr/src
/var/log
/var/tmp
/var/db
/var/cache/edb
$stage4Location
`echo $CCACHE_DIR`"

# files/devices/folders, which need to be backed up (preserve folder structure).
# don't touch it unless you know what you are doing! no recursive backup of folders.
# use $default_include_folders instead.
default_include_files="
/dev/null
/dev/console
/home
/mnt
`find /mnt -name .keep`
/proc
/sys
/tmp
/usr/portage
/usr/src
/var/log/emerge.log
/usr/src/linux-`uname -r`/.config"

# folders, which need to be backed up recursively on every backup.
# don't touch it unless you know what you are doing! the reason for this
# variable is that some users add /var to the $default_exclude_list. here
# we ensure that portage's memory is backed up in any case.
default_include_folders="
/var/db"

# IMPORTANT: A minimal backup will EXCLUDE files/folders listed here. A custom backup will
# include/exclude these files/folders depening on your answer.
custom_include_list="
/home/*
/usr/src/linux-`uname -r`"

# add files/folders here which are subfolders of a folder listed in $custom_include_list which should NOT
# be backed up. eg.
#custom_exclude_list="/home/foo/mp3 /home/foo/downloads /home/foo/.*"
custom_exclude_list=""

# Only files/folders within the $custom_include_list are checked against these patterns
# custom_exclude_pattern="*.mp3 *.iso"
custom_exclude_pattern=""

# the find_command
find_command="$find /*"

# don't backup anything which matches pattern listed in $default_exclude_pattern
for pattern in $default_exclude_pattern; do
        find_command="$find_command -not -name $pattern"
done

# assemble the find_command
function find_files()
{
        for folder in $default_exclude_list; do
                find_command="$find_command -path $folder -prune -o"
        done

        find_command="$find_command -print"

        for i in $default_include_files; do
                find_command="echo $i; $find_command"
        done

        for i in $default_include_folders; do
                if [ -d $i ]; then
                        find_command="$find $i; $find_command"
                else
                        find_command="echo $i; $find_command"
                fi
        done
}

# check the exclude/include variables for non-existing entries
function verify()
{
        for i in $1; do
                if [ ! -e "`echo "$i" | cut -d'=' -f2 | cut -d'*' -f1`" -a "$i" != "/lost+found" -a "$i" != "$stage4Location" ]; then
                        echo "ERROR: `echo "$i" | cut -d'=' -f2` not found! Check your "$2
                        exit 0
                fi
        done
}

# check input parameters
while [ $1 ]; do
        case  $1 in
        "-h" | "--help")
                echo -e $help
                exit 0;;
        "-v" | "--verbose")
                verbose=$1;;
        "-s" | "--split")
                tar_output="--split";;
        "");;
        *)
                echo -e $help
                exit 0;;
        esac
        shift
done

echo ""

# check folder/files listed in $default_exclude_list exist
verify "$default_exclude_list" "\$default_exclude_list"

# check files listed in $default_include_files exist
verify "$default_include_files" "\$default_include_files"

# check folder listed in $default_include_folders exist
verify "$default_include_folders" "\$default_include_folders"

#check folder listed in $custom_include_list exist
verify "$custom_include_list" "\$custom_include_list"

#check folder listed in $custom_exclude_list exist
verify "$custom_exclude_list" "\$custom_exclude_list"

# print out the version
 echo -e "\nBackup script $version"
 echo -e "=================="

# how do you want to backup?
echo -e "\nWhat do you want to do? (Use CONTROL-C to abort)\n
Fast (tar.gz):
 (1) Minimal backup
 (2) Interactive backup

Best (tar.bz2):
 (3) Minimal backup
 (4) Interactive backup\n"

while [ "$option" != '1' -a "$option" != '2' -a "$option" != '3' -a "$option" != '4' ]; do
        echo -en "Please enter your option: "
        read option
done

case $option in
[1,3])
        stage4Name=$stage4Location/$stage4prefix-minimal.tar;;

[2,4])
        stage4Name=$stage4Location/$stage4prefix-custom.tar

        for folder in $custom_include_list; do
                echo -en "\nDo you want to backup" `echo "$folder" | cut -d'=' -f2`"? (y/n) "
                read answer
                while [ "$answer" != 'y' -a "$answer" != 'n' ]; do
                        echo -en "Do you want to backup" `echo "$folder" | cut -d'=' -f2`"? (y/n) "
                        read answer
                done
                if [ "$answer" == 'n' ]; then
                        find_command="$find_command -path $folder -prune -o"
                else
                        custom_find="$find $folder"
                        for i in $custom_exclude_pattern; do
                                custom_find="$custom_find -name $i -o"
                        done
                        for i in $custom_exclude_list; do
                                custom_find="$custom_find -path $i -prune -o"
                        done
                        find_command="$custom_find -print; $find_command"
                fi
        done ;;
esac

# add $custom_include_list to the $default_exclude_list as we assembled
# $custom_find with $custom_include_list already.
default_exclude_list="$default_exclude_list $custom_include_list"

case $option in
[1,2])
        stage4postfix="gz"
        zip="--gzip";;

[3,4])
        stage4postfix="bz2"
        zip="--bzip2";;
esac

# mount boot
echo -e "\n* mounting boot"
mount /boot >/dev/null 2>&1

# find the files/folder to backup
find_files
find_command="($find_command)"

# create the final command
if [ "$tar_output" == "--file" ]; then
        tar_command="$find_command | $tar $zip $tarOptions $verbose --file $stage4Name.$stage4postfix --no-recursion -T -"
else
        tar_command="$find_command | $tar $zip $tarOptions $verbose --no-recursion -T - | split $split_options - "$stage4Name.$stage4postfix"_"
fi

if [ "$verbose" ]; then
        echo -e "\n* creating the stage4 in $stage4Location with the following command:\n\n"$tar_command
fi

# everything is set, are you sure to continue?
echo -ne "\nDo you want to continue? (y/n) "
read answer
while [ "$answer" != 'y' ] && [ "$answer" != 'n' ]; do
        echo -ne "Do you want to continue? (y/n) "
        read answer
done

if [ "$answer" == 'y' ]; then
        # check whether the file already exists.
        if [ "$tar_output" == "--split" ]; then
                overwrite="`ls "$stage4Name.$stage4postfix"_* 2>&1 | grep -v 'No such file'`"
        else
                overwrite="$stage4Name.$stage4postfix"
        fi

        if [ -a "`echo "$overwrite" | grep "$overwrite" -m1`" ]; then
                echo -en "\nDo you want to overwrite $overwrite? (y/n) "
                read answer
                while [ "$answer" != 'y' ] && [ "$answer" != 'n' ]; do
                        echo -en "Do you want to overwrite $overwrite? (y/n) "
                        read answer
                done
                if [ "$answer" == 'n' ]; then
                        echo -e "\n* There's nothing to do ... Exiting"
                        exit 0;
                fi
        fi

        # if necessary, create the stage4Location
        if [ ! -d "$stage4Location" ] ; then
                echo "* creating directory $stage4Location"
                mkdir -p $stage4Location
        fi

        echo -e "\n* Please wait while the stage4 is being created.\n"

        # do the backup.
        sh -c "$tar_command"

        # finished, clean up
        echo -e "\n* stage4 is done"
        echo "* umounting boot"
        umount /boot >/dev/null 2>&1

        # Integrity check
        echo -e "* Checking integrity"
        if [ "$zip" == "--gzip" ]; then
                zip="gzip"
        else
                zip="bzip2"
        fi

        if [ "$tar_output" == "--split" ]; then
                if [ "`cat "$stage4Name.$stage4postfix"_*"" | $zip --test 2>&1`" != "" ]; then
                        echo -e "* Integrity check failed. Re-run the script and check your hardware."
                        exit -1
                fi
        else
                if [ "`$zip --test  $stage4Name.$stage4postfix 2>&1`" != "" ]; then
                        echo -e "* Integrity check failed. Re-run the script and check your hardware."
                        exit -1
                fi
        fi

        # everything went smoothly
        echo -e "* Everything went smoothly. You successfully created a stage4."

else
        echo -e "\n* There's nothing to do ... Exiting"
fi