#!/bin/sh
#
# $Id$
#

interactive()
{
	local _file

	echo 'add *'
	for _file in `eval echo $"excludes"_$1`; do
		echo "delete ${_file}"
	done
	echo "extract"
	sleep 1
	echo "1"
}

get_from_conf()
{
	local _section _option _url _i _out

	if [ $# -ne 2 -o -z "$1" -o -z "$2" ]; then
		return 1
	fi

	_section="$1"
	_option="$2"

	_url="${rpc}/getconf?ident=${profile}&section=${_section}&option=${_option}"
	_i="0"
	while [ ${_i} -lt "${max_tries}" ]; do
		if _out="$(fetch -q -o - "${_url}" 2> /dev/null)"; then
			break
		fi
		# Do not DoS rpc. Use incrimental sleep.
		sleep ${_i}
		_i=$((${_i} + 1))
	done
	if [ "${_i}" -eq "${max_tries}" ]; then
		echo "RPC is unreachable (max tries reached)"
		exit 1
	fi

	if [ "${_out}" != "None" ]; then
		echo "${_out}"
		return 0
	fi

	return 1
}

clean_tmp_files()
{
	rm -rf ${tmp_files}
}

fetch_pkgng()
{
	echo -n "===> Fetching pkgng binary... "
	if ! fetch -q -o "${pkgng_cmd%% *}" "${pkgng_url}"; then
		echo "Cannot fetch"
		exit 1
	fi
	if ! chmod 0555 "${pkgng_cmd%% *}"; then
		exit 1
	fi
	echo "Done"
}

fetch_kernel()
{
	local _url

	_url="${repo}/${kernels_path}/${kernel}"
	echo -n "===> Fetching ${_url}... "
	if ! fetch -q "${_url}"; then
		exit 1
	fi
	tmp_files="$tmp_files ${dir}/${kernel}"
	if [ "`md5 -q ${kernel}`" != "`fetch -q -o - "${_url}.md5"`" ]; then
		echo -e "\nBad checksum for ${kernel}"
		exit 1
	fi
	echo "Done"
}

fetch_dumps()
{
	local _dump _url

	for _dump in $dumps; do
		_dump="${_dump}.dump.bz2"
		_url="${repo}/${dumps_path}/${_dump}"
		echo -n "===> Fetching ${_url}... "
		if ! fetch -q "${_url}"; then
			exit 1
		fi
		tmp_files="$tmp_files ${dir}/${_dump}"
		if [ "`md5 -q ${_dump}`" != "`fetch -q -o - "${_url}.md5"`" ]; then
			echo -e "\nBad checksum for ${_dump}"
			exit 1
		fi
		echo "Done"
	done
}

unset_schg()
{
	echo -n "===> Removing immutable flag... "
	cd ${prefix}/
	find -x bin/ lib/ libexec/ rescue/ sbin/ \
	usr/bin/ usr/include/ usr/lib/ usr/lib32/ \
	usr/libdata/ usr/libexec/ usr/sbin/ usr/share/ \
	-flags schg -exec chflags noschg {} \+ || true
	echo "Done"
}

delete_old()
{
	local _item

	echo -n "===> Removing old files and sysctls... "
	cd ${prefix}/
	if [ "${delete_files_flag}" -eq 1 ]; then
		echo ${old_files} | xargs rm -f
		echo ${old_dirs} | xargs rmdir 2> /dev/null || true
	fi

	for _item in ${old_sysctls}; do
		sed -e "/${_item}/d" -i "" etc/sysctl.conf
	done

	if [ "${pkgng}" -eq 1 ]; then
		${pkg_delete_cmd} zoneinfo* > /dev/null 2>&1
	fi

	echo "Done"
}

installng_system()
{
	local _rc _pkg

	echo -n "===> Installing kernel and base... "
	if ! ${pkgng_cmd} update > /dev/null; then
		exit 1
	fi
	_rc=0
	for _pkg in ${pkgng_system_list}; do
		if ! ${pkg_add_cmd} "${_pkg}"; then
			if [ "${_rc}" -eq 0 ]; then
				echo
				_rc=1
			fi
			echo "Cannot install ${_pkg}"
			continue
		fi
	done

	if [ "${_rc}" -eq 1 ]; then
		echo "Too many errors, exit."
		exit 1
	fi

	echo "Done"
}

install_kernel()
{
	local _items _item

	echo -n "===> Installing kernel... "

	cd ${prefix}/
	_items="boot/modules boot/kernel"
	for _item in ${_items}; do
		rm -rf ${_item}.old
		if [ -d ${_item} ]; then
			mv ${_item} ${_item}.old
		fi
		mkdir -p -m 0755 ${_item}
	done
	tar -C boot/kernel -xyf ${dir}/${kernel}

	echo "Done"
}

restore_dumps()
{
	local _dump

	for _dump in $dumps; do
		echo -n "===> Restoring ${_dump}... "
		if [ "${_dump}" = "root" ]; then
			cd ${prefix}/
		else
			if [ ! -d ${prefix}/${_dump} ]; then
				mkdir -m 0755 ${prefix}/${_dump}
			fi
			cd ${prefix}/${_dump}
		fi
		interactive ${_dump} | restore -u -i -P "bzcat ${dir}/${_dump}.dump.bz2" 2> /dev/null
		echo "Done"
	done

	cd ${prefix}/
	# Fix permissions
	chmod 755 usr/lib32
	# Sometime needs this
	#mount -t devfs devfs dev
	# Cut motd
	#sed -e '5,$d' -i '' etc/motd
}

add_new()
{
	local _item

	echo -n "===> Adding new files and sysctls... "
	cd ${prefix}/
	if [ ! -d etc/rc.conf.d ]; then
		mkdir -m 755 etc/rc.conf.d
	fi
	echo 'ddb_enable="YES"' > etc/rc.conf.d/ddb
	echo 'script lockinfo=show locks; show alllocks; show locktree; show lockedbufs; show lockedvnods
script kdb.enter.panic=textdump set; capture on; run lockinfo; show pcpu; bt; ps; alltrace; capture off; call doadump; reset
script kdb.enter.default=textdump set; capture on; bt; ps; capture off; call doadump; reset' > etc/ddb.conf

	for _item in ${new_sysctls}; do
		if grep -q "^${_item%=*}=" etc/sysctl.conf; then
			sed -e "s/^${_item%=*}=.*/${_item}/" -i "" etc/sysctl.conf
		else
			echo "${_item}" >> etc/sysctl.conf
		fi
	done

	echo "Done"
}

post_world()
{
	local _post_world_script _chroot

	echo -n "===> Running post_world script... "
	if ! _post_world_script="$(eval get_from_conf upgrade post_world)"; then
		echo "No post world script given - skipping step"
		return 0
	fi

	_chroot=${prefix:+chroot $prefix}
	if ${_chroot} sh -c "${_post_world_script}"; then
		echo "Done"
	else
		echo "Failed"
	fi
}

get_pkg_list()
{
	local _types _type _url _pkg_list

	if [ "${keep_pkg}" -eq 1 ]; then
		pkg_list=$(${pkg_info_cmd} | sed -e 's#.*/##' | egrep -v "${exclude_pkg}")
	else
		_types="packages package_extras"
		for _type in ${_types}; do
			if ! _pkg_list=$(eval get_from_conf packages ${_type}); then
				continue
			fi
			pkg_list="${pkg_list} ${_pkg_list}"
		done
		if [ -z "${pkg_list}" ]; then
			echo "Can't get package list from: ${rpc}"
			exit 1 
		fi
	fi
	if _pkg_list=$(eval get_from_conf packages "package_excludes"); then
		pkg_list="$(echo "${pkg_list}" | grep -vx "${_pkg_list}")"
	fi
}

delete_pkgs()
{
	echo -n "===> Removing all packages... "
	${pkg_delete_cmd} -a > /dev/null 2>&1
	echo "Done"
}

install_pkgs()
{
	local _pkg_list _err_pkg _pkg _out

	echo -n "===> Installing packages... "
	cd ${dir}
	> ${logfile}
	_pkg_list=$(echo -e "${required_pkg}\n${pkg_list}" | sort -u)
	_err_pkg=""
	for _pkg in ${_pkg_list}; do
		if [ "${pkgng}" -eq 1 ]; then
			if ${pkgng_cmd} info -e ${_pkg}; then
				continue
			fi
		fi
		if ! _out=`${pkg_add_cmd} ${_pkg} 2>&1`; then
			_err_pkg="${_err_pkg} ${_pkg}"
		fi
		echo "${_out}" >> ${logfile}
	done
	echo "Done"

	if [ -n "${_err_pkg}" ]; then
		echo "Packages that installed with errors:${_err_pkg}"
	fi
}

post_packages()
{
	cd ${prefix}/

	if [ -x usr/local/bin/fuser ]; then
		chmod +s usr/local/bin/fuser
	fi

	# Remove old variables that must be in login.conf
	sed -e "/PACKAGEROOT/d" -i "" etc/profile
	sed -e "/PACKAGESITE/d" -i "" etc/profile

	if [ "${delete_libs_flag}" -eq 1 ]; then
		echo ${old_libs} | xargs rm -f
	fi

	if [ "${pkgng}" -eq 1 ]; then
		rm -f usr/sbin/pkg_* usr/share/man/man1/pkg_*
		if [ ! -f usr/local/etc/pkg.conf ]; then
			echo "PACKAGESITE: $PACKAGESITE" > usr/local/etc/pkg.conf
			echo "PKG_CACHEDIR: /var/tmp/cache" >> usr/local/etc/pkg.conf
		fi
	fi
}

world()
{
	if [ "${pkgng}" -eq 1 ]; then
		fetch_pkgng
	else
		fetch_kernel
		fetch_dumps
	fi
	unset_schg
	delete_old
	if [ "${pkgng}" -eq 1 ]; then
		installng_system
	else
		install_kernel
		restore_dumps
	fi
	add_new
	post_world
}

packages()
{
	get_pkg_list
	delete_pkgs
	install_pkgs
	post_packages
}

set_globals_world()
{
	local _kernel

	new_sysctls=$(eval get_from_conf upgrade new_sysctls)
	old_sysctls=$(eval get_from_conf upgrade old_sysctls)

	if [ "${delete_files_flag}" -eq 1 ]; then
		old_dirs=$(eval get_from_conf upgrade old_dirs)
		old_files=$(eval get_from_conf upgrade old_files)
	fi

	# Options below are not requied for pkgng
	if [ "${pkgng}" -eq 1 ]; then
		return 0
	fi

	if ! excludes_root=$(eval get_from_conf upgrade excludes_root); then
		echo "Can't get \"excludes_root\" from: ${rpc}"
		exit 1
	fi

	dumps="root"
	if ! dumps_path=$(eval get_from_conf dumps dumpsdir); then
		echo "Can't get \"dumpsdir\" from: ${rpc}"
		exit 1
	fi

	if ! _kernel=$(eval get_from_conf kernel kernel); then
		echo "Can't get \"kernel\" from: ${rpc}"
		exit 1
	fi
	kernels_path="${_kernel%/*}"
	_kernel="${_kernel#*/}"
	kernel="${_kernel%.sh}.tbz"
}

set_globals_packages()
{
	local _packageroot _repo_ident _tmp

	pkg_list=""
	exclude_pkg="apr|expat|linux_base|neon|perl|scprotect|zoneinfo"
	if [ -n "${exclude}" ]; then
		exclude_pkg="${exclude_pkg}|${exclude}"
	fi
	
	if [ "${delete_libs_flag}" -eq 1 ]; then
		old_libs=$(eval get_from_conf upgrade old_libs)
	fi

	pkg_add_cmd="pkg_add -r -F"
	pkg_info_cmd="pkg_info -aoq"
	pkg_delete_cmd="pkg_delete -f"
	if [ -n "${prefix}" ]; then
		pkg_add_cmd="${pkg_add_cmd} -C ${prefix}/"
		pkg_info_cmd="chroot ${prefix}/ ${pkg_info_cmd}"
		pkg_delete_cmd="chroot ${prefix}/ ${pkg_delete_cmd}"
	fi

	if ! _packageroot=$(eval get_from_conf packages packageroot); then
		echo "Can't get \"packageroot\" from: ${rpc}"
		exit 1
	fi
	if ! _repo_ident=$(eval get_from_conf packages repo_ident); then
		echo "Can't get \"repo_ident\" from: ${rpc}"
		exit 1
	fi
	if [ "${pkgng}" -eq 1 ]; then
		exclude_pkg="apr|compat7x|expat|fuser|linux_base|neon|perl|scprotect|zoneinfo"
		pkgng_system_list="pkg freebsd-rescue freebsd-kernel freebsd-base freebsd-lib32 freebsd-zoneinfo"
		pkgng_cmd="/rescue/pkg"
		if [ -n "${prefix}" ]; then
			pkgng_cmd="${pkgng_cmd} -c ${prefix}/"
		fi
		pkg_add_cmd="${pkgng_cmd} install -f -q -y"
		pkgng_url="${_packageroot}/packages/${_repo_ident}/pkg-static"
		if ! _tmp=`mktemp -d /var/tmp/pkgng.XXXXX`; then
			exit 1
		fi
		tmp_files="$tmp_files ${_tmp}"
		export PKG_CACHEDIR="${_tmp}"
		export PACKAGESITE="${_packageroot}/packages/${_repo_ident}/"
		export ABI="freebsd:9:x86:64"
	else
		export PACKAGESITE="${_packageroot}/packages/${_repo_ident}/Latest/"
	fi
}

usage()
{
	echo "usage: $(basename $0) [-C] [-d] [-D] [-e exclude] [-h] [-k] [-l logfile] [-n] -p profile [-P prefix] [-r URL] [-R URL] [world|packages]"
	echo ""
	echo "  Options:"
	echo "    -C            do not clean tmp files"
	echo "    -d            do not delete old files"
	echo "    -D            do not delete old libs"
	echo "    -e exclude    exclude packages (regexp)"
	echo "    -h            print this message"
	echo "    -k            keep old package list (try to reinstall all)"
	echo "    -l logfile    log file"
	echo "    -n            use pkgng to upgrade system (by default)"
	echo "    -p profile    profile name"
	echo "    -P prefix     prefix to install"
	echo "    -r URL        repository URL"
	echo "    -R URL        RPC URL"
	echo ""
	echo "  Targets:"
	echo "    world         install latest kernel and world"
	echo "    packages      update all packages"
	echo ""

	exit 1
}

# XXX perl missed here
required_pkg="bash
compat7x-amd64
flock
gawk
lsof
ngrep
pam_mkhomedir
pprotectd
pstree
python
rsync
smartmontools
subversion
sudo
vim-lite"
dir=$(pwd)
max_tries="10"
tmp_files=""

clean_tmp_files_flag="1"
delete_files_flag="1"
delete_libs_flag="1"
exclude=""
keep_pkg="0"
logfile="/dev/null"
pkgng="1"
profile=""
prefix=""
repo="http://distillatory.yandex.ru"
rpc="${repo}/dhcp/rpc"

while getopts "CdDe:hkl:np:P:r:R:" opt; do
	case "$opt" in
	C) clean_tmp_files_flag="0" ;;
	d) delete_files_flag="0" ;;
	D) delete_libs_flag="0" ;;
	e) exclude="$OPTARG"; echo "-e option has broken"; exit 1 ;;
	h) usage ;;
	k) keep_pkg="1" ;;
	l) logfile="$OPTARG" ;;
	n) pkgng="1" ;;
	p) profile="$OPTARG" ;;
	P) prefix="$OPTARG" ;;
	r) repo="$OPTARG" ;;
	R) rpc="$OPTARG" ;;
	*) usage ;;
	esac

	shift $(($OPTIND - 1))
done

umask 0022

if [ "$(id -u)" -ne 0 ]; then
	echo "You must be root"
	exit 1
fi

if [ -z "${profile}" ]; then
	echo "You must specify profile name"
	exit 1
fi

if ! fetch -q -o /dev/null "${rpc}/getconf?ident=${profile}" 2> /dev/null; then
	echo "Profile doesn't exist"
	exit 1
fi

if [ "${clean_tmp_files_flag}" -eq 1 ]; then
	trap "clean_tmp_files" EXIT
fi

if [ $# -eq 0 ]; then
	set_globals_world
	set_globals_packages
	world
	packages
elif [ $# -eq 1 ]; then
	case $1 in
	world)
		set_globals_world
		set_globals_packages
		world
		;;
	packages)
		set_globals_packages
		packages
		;;
	*)
		echo "Wrong target"
		usage
		;;
	esac
else
	echo "Too many arguments"
	usage
fi

exit 0
