Добрый час, Хабровчане!
Хочу поделиться своим опытом автоматизации процесса установки и настройки FreeBSD с помощью sh (bash). Дело было так:
Однажды в компании возникла необходимость поднять несколько серверов на FreeBSD. Поставив одну, следом за ней вторую и третью ось, мы с коллегой (в штате всего два айтишника) задумались в сторону автоматизации этого процесса путем написания скрипта, выполняющего настройку свежеустановленной ОС. Задача написания легла на мои плечи. Коллега занялся решением вопроса автоматической установки, о чем я расскажу в другом посте. Итак, приступим!
Сразу уточню: скрипт писался исключительно для себя, поэтому некоторый код не оформлен должным образом или просто «не идеален». Но, тем не менее, он работает!
Скрипт состоит из 3 файлов: файл-загрузчик, основной исполняемый файл и файл с библиотеками. Начну по порядку.
loader.sh
#!/bin/sh lib1='parser.sh' #CORE FILE lib2='parserlib.sh' #LIB FILE #Defining variables --begin-- FTPServer='ftp://10.10.10.50' #FTP server address FileName='tarball.tar.gz' #Default tarball name LongVersion=`uname -r` #Long name of current OS version ShortVersion=${LongVersion%%-*} #Calculatig short version of OS, like "8.3" (DON'T TOUCH THIS LINE, please!) LocalDirectory='/tmp/script/' #Temporary directory. You can manualy clean it later. If it's not exist - just relax, script will create it for you :-) DataDirectory='/var/parser-md5-list/' #Directory with datafiles LockFile='EditLocker.p' #Lock file. Needed to prevent changes in files by script after manual editing. EditLock="$DataDirectory$LockFile" #Absolute LockFile path forcer='-no-force' #Variable must be not empty in case if -f key was not specified #Defining variables --end-- usage (){ echo "Only acceptable options: -f to force rebuild without check -s to force execution without restart -d to delete locker file (needed if files was edited manyally or by system, but now you want to rebuild it) -v <version> to manually specify FreeBSD version. Please be sure to specify it correctly! Example: ./loader.sh -v 9.1 " } #Defining options --begin-- if [ $# -ge 1 ] then while getopts fsdv: opt; do case "$opt" in f) forcer='-f';; s) skip='-s';; d) DL='-d';; v) ShortVersion="$OPTARG";; [?]) usage; exit 1;; esac done fi #Defining options --end-- echo "FreeBSD configuration tool V 1.0" echo "Detected OS version: $LongVersion" echo "Applying tarball for version $ShortVersion" echo; echo "Downloading files..." if [ "$DL" == "-d" ] then touch $EditLock #If file does not exists - this line will create it (needed to avoid error message from rm) rm $EditLock #Remove Lock file fi if ! [ "$skip" == "-s" ] then if ! [ -e run.sh ] then fetch "$FTPServer/FreeBSD/loader.sh" touch run.sh echo "#!/bin/sh clear echo \"*********************************************** * Loader file was updated. Process restarted. * *********************************************** \" " >> run.sh echo "./loader.sh $@" >> run.sh chmod +x run.sh ./run.sh exit 1 else rm run.sh fi fi fetch "$FTPServer/FreeBSD/$lib1" fetch "$FTPServer/FreeBSD/$lib2" chmod +x $lib1 chmod +x $lib2 . $lib1 #Attaching core file to process if [ $? -ne 0 ] #Checking for errors then echo "ERROR! Library $lib1 not found!" #Core file does not exist. exit 1 fi LetItGo $FTPServer $FileName $ShortVersion $LocalDirectory $forcer $DataDirectory $EditLock
parser.sh
#!/bin/sh -x LetItGo() #Body of script { lib1='parserlib.sh' #Lib file . $lib1 #Attaching lib file to process if [ $? -ne 0 ] #Checking for errors then echo "ERROR! Library $lib1 not found!" #Lib file does not exist. kill $$ exit 1 fi #Defining variables --begin-- server=$1 #FTP server address file=$2 #Default tarball name ver=$3 #Version of OS, like "8.3" LocalDir=$4 #Temporary directory. You can manually clean it later. If it's not exist - just relax, script will create it for you :-) DataDirectory=$6 #Directory with data files EditLock=$7 #This file needed to prevent overriding for manually edited files. #Defining variables --end-- cdOrCreate $DataDirectory #Creating data directory if not exists cdOrCreate $LocalDir #Enter temporary directory for file downloads dirchek=`pwd`/ if [ "$dirchek" == "$LocalDir" ] #Checking current directory then rm -rf * #If script successfully entered the temp directory - it will be cleaned else echo "$LocalDir is not accesible! Please check permissions!" kill $$ exit 1 fi fetch "$server/FreeBSD/$ver/$file" #Download tarball hshchk=$(md5 $file) HashCheck ${hshchk##* } $LocalDir $file $5 $DataDirectory cd $LocalDir echo "Extracting files" tar -zxf $file #Unpack it echo "DONE! " rm $file #Remove tarball echo "Tarball was removed from local server." touch $EditLock echo "Lockfile created: $EditLock" for f in $( find $LocalDir ); do #Proceed all files one by one if [ -f $f ] then #check file for manual changes NEWFILE=${f#$LocalDir} NEWFILE=/${NEWFILE#*/} HCK=$(md5 $f) HCK=${HCK##* } #TIMEEDIT="$NEWFILE `stat -f %Sm -t %Y%m%d%H%M%S "$NEWFILE"`" EDITC="$NEWFILE $HCK" CHECK=`grep "$EDITC" "$EditLock"` SIMPLECHECK=`grep "$NEWFILE" "$EditLock"` #You may add your own subtree in additional elif below (for example: immediately script execution, assigning permissions, e.t.c.) if [ "`expr "$f" : '.*\(/Merge/\)'`" == "/Merge/" ] #If file should be merged then TempPath=${f##*/Merge} #Cut filepath. Original location will remain echo; echo "Merge: $f --> $TempPath" if ! [ -f $TempPath ] #If original file exist then MoveToLocal $f Merge #Then just replace it by new one else MergeFiles $f $TempPath #Else - merge new file to the old one line by line sort -u $TempPath > $TempPath.tmp #Delete repeating lines if exists mv -f $TempPath.tmp $TempPath #Rewriting merged file by filtered unique data CleanEOL $TempPath #Cleaning empty lines fi echo "DONE!" elif [ "`expr "$f" : '.*\(/Replace/\)'`" == "/Replace/" ] #If file should be replaced then if [ "$EDITC" == "$CHECK" ] || [ "$SIMPLECHECK" = '' ] then echo; echo "Replace: $f --> ${f##*/Replace}" MoveToLocal $f Replace #Then just replace it echo "DONE!" echo "$EDITC" >> $EditLock sort -u $EditLock > $EditLock.tmp #Delete repeating lines if exists mv -f $EditLock.tmp $EditLock else echo; echo "File $NEWFILE was edited manually. Skipped. Use -d key to ignore it." fi elif [ "`expr "$f" : '.*\(/Scripts/\)'`" == "/Scripts/" ] #If tarball contains a scripts, which should have +x permissions then echo; echo "Replace script: $f --> ${f##*/Scripts}" MoveToLocal $f Scripts #Then replace it (scripts cannot be merged) chmod +x ${f##*/Scripts} #And give eXecution permissions echo "DONE!" else echo; echo "DON'T match. Cannot proceed $f. Skipping." #This message means there is another subtree in tarball. It should be removed or described here fi fi done echo; echo "====================================================================" echo; echo "Cleaning temporary files" cd $LocalDir dirchek=`pwd`/ if [ "$dirchek" == "$LocalDir" ] #Checking current directory then rm -rf * echo "Succesfully cleaned" else echo "Temporary files was NOT deleted!" fi echo "DONE!" echo " Tarball was successfully applied." echo "To re-apply it again - use force key (-f)." #Finished }
И, собственно, функции:
parserlib.sh
#!/bin/sh -x cdOrCreate() #Enter the directory. Create if it's not exist, then enter. Arguments: 1) Path to directory (alternate or absolute). { if ! [ -d $1 ] #If directory does not exists then mkdir -p "$1" #Then create it fi cd "$1" #Enter the directory } MoveToLocal() #Create path and move file there (or replace existing file). Arguments: 1) full filename with full filepath 2) Folder identifyer, without slashes. { TempPath=${1##*/$2} #Deleting folder identifier from path AbsolutePath=${TempPath%/*} #Completing absolute path cdOrCreate $AbsolutePath #See cdOrCreate() description cd ${1%/*}"/" #Entering directory with file for move mv ${1##*/} $AbsolutePath"/"${1##*/} #Move file to new (absolute path) location } MergeFiles() #Using for check each file from "Merge" subtree and replace lines, or add line to end of file if not exist (?). Files MUST BE in conf syntax. { cat $1 | while read line do lineName=${line%=*} #Calculating key name lineName="$lineName=" lineHashedName=${lineName##\#} #Calculating name if commented sed -i -e 's/^'$lineHashedName'.*/'$line'/g' $2 #Replace line with key (uncommented) sed -i -e 's/^#'$lineHashedName'.*/'$line'/g' $2 #Replace line with key (commented by one hash) echo "$line" >> $2 #Append key to the end of file (dublicates will be sorted). done } CleanEOL() #This function needed for delete ^M from end of replaced lines and delete every empty line. Arguments: 1) Filename with path. { mv $1 tempconfig.conf cat tempconfig.conf | tr -d '\r' > tempconfig.conf.1 #Deleting ^M grep '.' tempconfig.conf.1 > $1 #Deleting empty lines and move file to original location rm tempconfig.conf* #Deleting temporary files } HashCheck() #Checks MD5 of tarball. Arguments: 1) Filename 2) Path 3) Tarball name 4) Flag (force rebuild existing installation) 5) Data directory { cdOrCreate "/var/parser-md5-list/" #See cdOrCreate description fpath=$5 #Location of currently downloaded tarball pointcheck=$4 #Force flag. If equal to "-f" then check will be skipped if ! [ -f $fpath ] #If checkfile does not exists then touch $fpath #Then create it echo $(date) >> $fpath #And write date and time into it elif ! [ "$pointcheck" == '-f' ] #If file exists and force flag was not specified then cat $1 | while read line #Then read date and time from existing file do #Show message echo " ===========================================================" echo " This tarball was applied at $line " echo " Use -f (force) to ignore this warning and rebuild anyway " echo "=========================================================== " cd "$2" #Enter directory which contains currently downloaded tarball rm "$3" #And delete tarball kill $$ #Kill parent process and exit exit 1 done esle #If file exists and -f was specified rm $fpath #Delete existing file touch $fpath #And create a new one fi }
На FTP сервере выполняется скрипт для создания архивов с настройками:
#!/bin/sh cdOrCreate() #Enter the directory. Create if it's not exist, then enter. Arguments: 1) Path to directory (alternate or absolute). { if ! [ -d $1 ] #If directory does not exists then mkdir "$1" #Then create it fi cd "$1" #Enter directory } cd /data/ftproot/FreeBSD/ cat list.txt | while read line do if [ "$1" == '-v' ] then echo " == Processing subtree for version $line ==" fi cdOrCreate $line rm tarball.tar.gz if [ "$1" == '-v' ] then tar -zcvf tarball.tar.gz * cd .. else tar -zcf tarball.tar.gz * cd .. fi done echo " DONE!" if ! [ "$1" == '-v' ] then echo "Use -v for detailed output." fi
В архив включаются все файлы, лежащие в поддиректориях, которые указаны в списке list.txt.
После распаковки архива скрипт проверяет ветки Merge и Replace. Для первой производится добавление или замена параметров в файлах конфигурации, в случае необходимости строки комментируются или раскомментируются. Для второй делается обычная замена файлов. Для каждого измененного файла сохраняется его MD5 в списке $DataDirectory$LockFile и, в случае повторных запусков скрипта, файлы с несоответствующим MD5 изменены не будут. Это было сделано для предотвращения отката изменений, сделанных администратором вручную.
Также, на случай предотвращения ошибочных изменений в скрипте сделана функция перезапуска через файл run.sh который создается, перезапускает скрипт и удаляется. В принципе — эту функцию легко выпилить.
Скрипт принимает следующие ключи (в любом порядке):
-f пропускает проверку на повторное применение архива
-s пропускает перезапуск скрипта
-d удаляет lockfile. Нужно для отката ручных изменений
-v VER принудительно указывает версию FreeBSD
Любой другой ключ вызовет функцию usage и скрипт завершит свою работу.
Также вы можете добавить свои варианты обработки поддиректорий в архиве. Для этого нужно описать их в файле parser.sh в ветке elif ниже соответствующего комментария.
Структура одного из моих архивов выглядит следующим образом:
Merge/boot/loader.conf
Merge/etc/rc.conf
Replace/usr/local/etc
Replace/usr/local/etc/svnup.conf
Replace/usr/share/skel
Replace/usr/share/skel/dot.cshrc
Replace/etc/ntp.conf
Replace/etc/adduser.conf
Replace/etc/portsnap.conf
Replace/root/.cshrc
Где после имени директории (у меня это Merge и Replace) сохраняется оригинальный путь к файлу. Имя директории и всё что находится до него убирается, далее файл обрабатывается кодом в соответствующей ему ветке if’а.
Скрипт написан с использованием только родных функций, т.е. будет работать на любой свежеустановленной фряхе.
Для запуска нужно сфетчить файл loader.sh, дать ему права на выполнение (chmod +x loared.sh) и, естественно, запустить.
Буду рад конструктивной критике, замечаниям и предложениям, т.к. прекрасно понимаю, что решение не идеально, и с удовольствием доработаю его.
P.S.: Очень извиняюсь за недописанный пост в пятницу. Случайно опубликовал копию и не заметил этого.
ссылка на оригинал статьи http://habrahabr.ru/post/198148/
Добавить комментарий