Автоматическая настройка FreeBSD

от автора

Добрый час, Хабровчане!

Хочу поделиться своим опытом автоматизации процесса установки и настройки 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/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *