#!/bin/bash
#
# __copy1__
# __copy2__
#
CMD=$(basename $0)
CMDVER="2.25"
CMDSTR="$CMD v$CMDVER __TOOLKIT_SIGNATURE__"

# abort on undefined vars (mostly typos)
#
set -e -u

export VERBOSE=true
export TMPDIR=${TMPDIR:-/tmp}
export RUNDIR="$TMPDIR/notexistent"
export CLASS=
export LOGNAME=${LOGNAME:-root}
export LANG="C"

# include common functions
. kusa-functions.sh


# -------------------------------------------------
# kickstart (needs to be done before anything else)
# -------------------------------------------------

# assure that default (distro files) are installed, this test is not
# 100% proof, but better than nothing
#
needs_defaults_install=true
for arg
do
	[ X"$arg" == X"-b" -o X"$arg" == X"--build" ] && needs_defaults_install=false
done
$needs_defaults_install && {
	files=$(ls $CONFDIR/conf.d/auto.00* 2>/dev/null) || :
	[ "$files" == "" ] && {
		echo -e "\n** kusa defaults not installed, doing it now ...\n"
		rm -rf /tmp/kusa.run
		status=0
		kusa-reconf -b || status=$?
		rm -rf /tmp/kusa.run
		[ $status != 0 ] && exit $status
	}
}
unset needs_defaults_install arg files




usage()
{
	trap '' 1 2 3 EXIT

	echo -e "
=== $CMDSTR == KUBiC Labs System Autoconfigurator ===

usage: $CMD [options] [module(s) ...]

running options:

  -n|--dry-run		dry-run (no execute, don't change anything)
     --exec-pre		dry-run but execute run-pre scripts

  -f|--force [ALL]	forces run (ignore already done modules)
  			use with caution! for safety, this flag works only
			if you pass a module list (use ALL to work on all modules)

classes/services options:

  -c|--class class	use this class instead of defined one, multiple classes
			allowed, pass them as comma separated list

  -s|--service name	request a service (multiple -s allowed)
  			define svclist in config for permament setting

  -x|--skip name	skip module name (multiple -x allowed)
  			define skiplist in config for permament setting


config options:

  -u|--update-conf	check for config files updates and download them if needed,
  			and stops (does NOT continue with system reconfiguration)

  -d|--download-conf	forces download of config files from central config server
  			(doesn't check is they are up-to-date)

  -U|--no-update-conf	don't try to update config

  -k|--keep-temps	don't remove temps on exit
  
  -K|--keep-sec		preserve secured config infos on exit (auto.??.adm* files)
  -S|--clean-sec	remove secured config infos on exit (both -K and -S overrides
  			kusa db definition common.keep_secinfos)

debug options:

  -m|--list-modules	echoes modules list, obtained from config files and after
  			a dummy run (see --build option below); temps not retained

  -b|--build		only build modules list and links (merging required classes)
			then stops and leave temps around to inspect

  -D|--debug		debug run

  -p|--pause		stops before each module run

  -v|--verbose		(ignored)

  -V|--version		print version and exists


other options:

  --abort-non-compliant	aborts when a requested module is not compliant
  			with OS release
  --skip-non-compliant	allows to skip non compliant modules and continue
" >&2

	# de-serialize vars
	avail_classes=$(echo $avail_classes | sed -e 's/common//')
	avail_services=$(echo $avail_services)
	avail_modules=$(echo $avail_modules)

	putwarning "CLASSES" $avail_classes
	putwarning "SERVICES" $avail_services
	putwarning "MODULES" $avail_modules

	[ X"$*" != X ] && {
		echo -e "error: $*" >&2
	}
	exit 1
}


cleanup()
{
	local last_stat=$?

	trap '' 1 2 3 ERR EXIT

	cd /tmp

	stty sane echoe echok 2>/dev/null || :

	[ $last_stat != 0 ] && echo -en "\nUNEXPECTED ERROR $last_stat ON LAST COMMAND"
	echo -en "\ncleaning: "

	F_REMOVE_TEMPS=${F_REMOVE_TEMPS:-"true"}
	F_REMOVE_SEC_CONF=${F_REMOVE_SEC_CONF:-"true"}
	RUNDIR=${RUNDIR:-""}

	$F_REMOVE_TEMPS && {
		[ "$RUNDIR" != "" -a -d "$RUNDIR" ] && {
			if echo "$RUNDIR" | grep -q '/tmp'
			then
				echo -n "tempdir "
				cd /tmp
				rm -rf $RUNDIR
			else
				echo -en "WARN tempdir NOT REMOVED (not tmp?: $RUNDIR) "
			fi
		}
	}

	$F_REMOVE_SEC_CONF && {
		[ -d $CONFDIR/conf.d ] && {
			echo "secinfos "
			cd $CONFDIR/conf.d
			files=$(ls auto.[0-9][1-9].adm* auto.[1-9][0-9].adm* 2>/dev/null) || :
			[ "X$files" != "X" ] && {
				echo "  " $files
				rm -f $files
				cd $CONFDIR
				rm -f timestamp config-timestamp config-urls
			}
		}
	}

	echo ""
	cd /tmp
	trap 1 2 3 ERR EXIT
	return 0
}



setup_directories()
{
	local dir=

	rm -rf $MODULESDIR ; mkdir -p $MODULESDIR

	for dir in conf.d done scripts files
	do
		[ -d $CONFDIR/$dir ] || mkdir -p $CONFDIR/$dir
	done

	[ -d $DONEDIR ]		|| mkdir -p $DONEDIR
	[ -d $BACKUPDIR ]	|| mkdir -p $BACKUPDIR
	[ -d $LOGDIR ]		|| mkdir -p $LOGDIR
}



# builds up $MODULESDIR directory in tempdir, linking modules dirs as listed
# by requested classes and subclasses
#
build_modules()
{
	decho "build_modules() called with: $*"

	local class=$1		; shift
	local buildstep=$1	; shift

	local wantedmodules=$*
	local modules=
	local stdmodules=
	local checkedmodules=
	local lsvcname=
	local mod=
	local modname=
	local dir=
	local subdir=
	local found=
	local dummymode=false
	local precedence=
	local num=0
	local error=

	$F_EXEC		|| dummymode=true
	$F_BUILD_ONLY	&& dummymode=true

	# the step is increremented so we can assure the right packages
	# precedence, note that the step always starts as '1', but int the
	# REQUIRED phase
	#
	BUILD_MODULES_STEP=$(expr $BUILD_MODULES_STEP + $buildstep)
	[ $BUILD_MODULES_STEP -gt 99 ] && {
		echo "error: build_modules() recursion too depth (loop?)" >&2
		exit 1
	}

	cd $LIBDIR

	# precedence is used to sort out module installation:
	#
	#	00	builtin-start
	#	03	local-pre and model modules
	#	07	common modules
	#	10-19	automatic required modules
	#	20	normal modules
	#	30	extra modules
	#	40	manual requested modules
	#	70	local modules (except local-pre)
	#	80	post-install modules
	#	99	builtin-cleanup
	#
	case $class in
	  builtin-start)	precedence="00"	;;
	  builtin-cleanup)	precedence="99" ;;
	  local-pre|model-*)	precedence="03" ;;
	  common*)		precedence="07" ;;
	  extra)		precedence="30" ;;
	  manual)		precedence="40" ;;
	  local*)		precedence="70" ;;
	  post*)		precedence="80" ;;
	  required*)		# required modules uses the recursion level to
	    			# decrease the precedence
	    			precedence=$(echo $class | sed -e 's/required//')
				precedence=$(expr 20 - $precedence)
				;;
	  *)			precedence="20" ;;
	esac

	# get standard modules list for this class (if available)
	#
	case $class in
	    builtin-*|model-*)
	    	stdmodules=$class
		;;
	    required*)
		stdmodules=$(cat $REQUIRES)
		class="($class)"
		:> $REQUIRES	# clean required list to allow recursion
		;;
	    postrequired*)
		stdmodules=$(cat $REQUIRES_POST)
		class="($class)"
		:> $REQUIRES_POST	# clean required list to allow recursion
		;;
	    extra)
		stdmodules=$(jtconf common.extramodules) || return 1
		;;
	    manual)
	    	;;
	    *)
		stdmodules=$(jtconf class.$class.modules) || \
			exit_missing_define class.$class.modules
		;;
	esac

	printbold 16 $class

	decho "\nstdmodules=$stdmodules"

	modules=${wantedmodules:-$stdmodules}
	
 	# allowed modules?
	#
	[ "X$ALLOWED_MODULES" != "X" ] && {
		error=false
		for mod in $modules
		do
			case $mod in
			  builtin*)
			  	;; # always ok
			  *)	in_list $mod $ALLOWED_MODULES || {
			  		echo -e "\n error: module '$mod' not allowed" >&2
			     		error=true
			     	}
			     	;;
			esac
		done
		$error && {
			echo >&2
			echo " allowed modules are: $ALLOWED_MODULES" >&2
			exit_err 1 "requested modules not in common.allowed_modules list"
		}
	}

 	# normalize modules (search directories)
	#
	for mod in $modules
	do
		found=false

		# searches module in (first found take precedence):
		#
		#  . CONFDIR/modules
		#  . LIBRARY/modules
		#
		# and then in DISTRIB subdirs:
		#
		#  . FULL_ID		eg: Ubuntu-12.04/
		#  . ID-CODENAME	eg: Devuan-ascii/
		#  . CODENAME		eg: ascii (can be confusing, better use ID-CODENAME)
		#  . ID			eg: Devuan
		#  . none
		#
		for dir in "$CONFDIR/modules/" "$LIBDIR/modules/"
		do
			for subdir in $(list_distro_variants "%s/") ""
			do
				[ -d "$dir$subdir$mod" ] && {
	       				checkedmodules="$checkedmodules $dir$subdir$mod"
					found=true
					break
				}
			done
		done
		$found || exit_err 1 "module '$mod' doesn't exists"
	done
	decho "1) checkedmodules=$checkedmodules"

	modules="$checkedmodules"
	checkedmodules=

	for mod in $modules
	do
		modname=$(basename $mod)

		# must be skipped?
		#
		grep -q "^$modname$" $SKIPLIST && {
			decho "$modname skipped (in skiplist)"
			continue
		}

		# depends on service request?
		#
		[ -f $mod/svc-name ] && {
			svcname=$(cat $mod/svc-name)
			service_requested "$svcname" || {
				decho "$modname skipped (service $svcname not requested)"
				continue
			}
		}
		checkedmodules="$checkedmodules $mod"
	done
	decho "2) checkedmodules=$checkedmodules"

	# copy checked modules dirs to temp modules dir, and create module list
	#
	# $checkemodules now contains full directory path of each module
	#
	for mod in $checkedmodules
	do
		modname=$(basename $mod)
		grep -q " $modname$" $MODULESLIST || {
			echo -n "$modname "
			cp -af $mod $MODULESDIR/. || exit $?
			# num is used to preserve precedence inside this
			# module block after the main precedence tag
			num=$(expr $num + 1)
			printf "%02d%s%02d %s\n" \
				$BUILD_MODULES_STEP $precedence $num \
				$modname >>$MODULESLIST

			# have required modules?
			#
			[ -s $mod/requires ] && {
				cat $mod/requires >>$REQUIRES
				$dummymode && echo -n "(r:" $(cat $mod/requires)") "
			}
			[ -s $mod/requires_post ] && {
				cat $mod/requires_post >>$REQUIRES_POST
				$dummymode && echo -n "(p:" $(cat $mod/requires_post)") "
			}
		}
	done
	echo

	# add optional required modules (done in recursive mode, don't checks
	# optional requested modules because they NEED to be added to the list)
	#
	while [ -s $REQUIRES -o -s $REQUIRES_POST ]
	do
		BUILD_REQUIRES_STEP=$(expr $BUILD_REQUIRES_STEP + 1)
		[ -s $REQUIRES ] && {
			build_modules required$BUILD_REQUIRES_STEP 0
		}
		[ -s $REQUIRES_POST ] && {
			build_modules postrequired$BUILD_REQUIRES_STEP 0
		}
	done

	return 0
}



apply_config()
{
	# sort modulelist, strip precedence id and get modules names
	#
	for mod in $(sed -e 's/.* //' $MODULESLIST)
	do
		export MODNAME=$(echo $mod | sed -e 's/^[0-9][0-9]-//')
		export MODRUNDIR="$MODULESDIR/$mod"
		export MODLOCALDB="$DONEDIR/$MODNAME.tmpdb"	# see later

		printbold 30 "MOD $MODNAME"


		# reset globals
		FILES_INSTALLED=""
		FILES_HAS_INSTALLED=false
		PKGS_HAS_INSTALLED=false
		SOMETHING_CHANGED=false

		local donefile="$DONEDIR/$MODNAME.done"
		local changed_files=
		local script=
		local tmp=
		local pkgs=
		local pkg=
		local file=
		local dest=
		local owner=
		local perms=
		local stage=
		local files=
		local default_owner="root:root"
		local default_mode="0440"
		local default_dir_mode="0775"
		local default_parse=true
		local is_ok=true
		local f_exec_pre=$F_EXEC_PRE

		cd $MODRUNDIR || exit 1

		case $MODNAME in
	  	  builtin-*)	   f_exec_pre=true	;;	# builtin modules always executes pre scripts
		esac

		# temporary database definitions can be placed into this file by run-pre
		# scripts; this file is per-module, and is cleaned before module run
		#
		rm -f $MODLOCALDB

		# 2025-02-02 kanna
		# if a proxmox system, relcompat is MANDATORY 
		$DISTRIB_IS_PVE && is_ok=false

		# checks system compatibility
		[ -f relcompat ] && {
			is_ok=false

			# now accept '*' as shortcut for 'all systems'
			if [ "X$(cat relcompat)" = "X*" ]
			then
				is_ok=true
			else
				for tmp in $(list_distro_variants "%s")
				do
					grep -q "^$tmp$" relcompat && {
						is_ok=true
						break
					}
				done
			fi
		}

		$is_ok || {
			if [ -f relcompat ]
			then
				echo -e "** unsupported release, needed:" $(cat relcompat)
			else
				echo -e "** unsupported release (no 'relcompat' file)"
			fi
			echo -e "   this system matches (distro+model):"
			echo -e "  " $(list_distro_variants "%s")"\n"
			$F_ABORT_NON_COMPLIANT && {
				echo "*aborted*"
				exit 1
			}
			MODNAME= MODRUNDIR= MODLOCALDB=
			continue
		}

		# must be skipped due wrong state?
		#
		$KUSA_STATE_DESKTOP_RUNNING && {
			[ -f state_no_desktop_running ] && {
				echo "(state: desktop running, skipped)"
				MODNAME= MODRUNDIR= MODLOCALDB=
				continue
			}
		}


		# if check/skip script exists, run it
		# true=continua, false=skip
		#
		script=$(filepath run-checkskip -f -x) && {
			$script || {
				echo "(skipped by $script)"
				MODNAME= MODRUNDIR= MODLOCALDB=
				continue
			}
		}


		# if last execution flagfile exists, use it as reference to build a list
		# of changed files
		#
		[ -f $donefile ] && {
			tmp=$(ls -d * $CHECK_FILES 2>/dev/null) || :
			changed_files=$(find $tmp -type f -newer $donefile 2>/dev/null) || :
		}


		changed_message=

		# if post module install script exists, run it
		# return true=already done, false=force execution
		#
		script=$(filepath run-checkdone -f -x) && {
			changed_message=$($script) || rm -f $donefile
		}

		# last check, force flag from command line?
		#
		$F_FORCE && rm -f $donefile

		# last done flagfile exists, and not changed files detected, skip
		#
		if [ -f $donefile -a ! "$changed_files" ]
		then
			echo "done on $(cat $donefile)"
			MODNAME= MODRUNDIR= MODLOCALDB=
			continue
		fi

		if [ "$changed_files" ]
		then
			changed_message=$(echo $changed_files)
			changed_message=$(printf "new: %-50.50s .." "$changed_message")
			# if any of 'run' scripts are changed, mark as 'sensible' change
			echo "$changed_files" | egrep -q "/run$" && SOMETHING_CHANGED=true
		fi

		echo "$changed_message"

		$F_PAUSE && {
			confirm "  continue running?" || exit 1
		}

		$F_PROFILING && {
			echo "  [P] time:   $(date)"
			echo "  [P] dusage: $(ku-dusage set /)"
		}

		# esegue se esiste script di check, se ritorna errore blocca tutto
		script=$(filepath run-check -f -x) && {
			$script || exit_err $?
		}

		# esegue se esiste script run preliminare (*prima* di installazione files)
		script=$(filepath run-pre -f -x) && {
			exec_script $script $f_exec_pre || exit_err $? "on script '$script'"
		}

		# stages:
		#
		#  install-pre	install mandatory files needed by subsequent pkgs istallation
		#		(eg: apparmor files, while standard files break pkgs update
		#		it they need to be customized)
		#
		#  pkgs-remove	remove all unneeded packages before any other action
		#
		#  pkgs-install	install needed packages
		#
		#  pkgs-postremove  try to remove (again) the unneeded packages, because
		#		the installation stage may have reinstalled them again, due
		#		silly depends
		#
		#  install	install custom files on the system
		#
		#  run-install	run the post-install script (if exists)
		#
		#  purge-init	purge installed init/upstart files, depending on $DISTRIB_HAS_UPSTART
		#
		set -e
		for stage in install-pre pkgs-remove pkgs-install pkgs-postremove \
				install run-install purge-init
		do
		  case $stage in
		    install-pre|install)
			files=$(list_install_files $stage)
			[ "$files" == "" ] && continue
			echo "  using installfiles: " $files

			# the install files are parsed themself, too
			#
			:> install.tmp
			for file in $files
			do
				parse_installfiles $file >>install.tmp		|| exit_err $?
			done

			exec 9<&0 <install.tmp
			while read file dest owner perms parseflag
			do
				parseflag="$parseflag" file="$file"
				case "$parseflag" in
					noparse|NOPARSE|false)	parseflag=false ;;
					parse|PARSE|true)	parseflag=true ;;
					*)			parseflag=$default_parse ;;
				esac

				case "$file" in
					:default_owner)
						default_owner=$dest; continue ;;
					:default_mode)
						default_mode=$dest; continue ;;
					:default_dir_mode)
						default_dir_mode=$dest; continue ;;
					:parse)
						default_parse=true ; continue ;;
					:noparse)
						default_parse=false ; continue ;;
					:link)
						updatelink $dest $owner || exit $?
						continue
						;;
					:dir)
						owner=${owner:-$default_owner}
						perms=${perms:-$default_dir_mode}
						if $F_EXEC
						then
							create_dir --fixperms $dest $owner $perms || exit_err $?
						else
							[ -d $dest ] || echo " $DUMMYTAG creating dir $dest $owner $perms"
						fi
						continue
						;;

					# external dir: owned by another module/package, so created if not
					# exists (to allow files installation), but don't change perms if exists
					#
					:extdir)
						owner=${owner:-$default_owner}
						perms=${perms:-$default_dir_mode}
						if $F_EXEC
						then
							create_dir $dest $owner $perms || exit_err $?
						else
							[ -d $dest ] || echo " $DUMMYTAG creating dir $dest $owner $perms"
						fi
						continue
						;;
					"")
						continue
						;;
				esac

				$f_exec_pre || {
					echo "$file" | grep -q "\.tmp$" && {
						echo " $DUMMYTAG install $file IGNORED"
						continue
					}
				}

				# now try to install file
				#
				# single or multiple (glob)?
				#
				files=$(ls $file 2>/dev/null) || :
				case $files in
				  "")	;;
				  *)	if [ $(echo "$files" | wc -l) == 1 -a "$files" == "$file" ]
				  	then
				  		# single file. reset $files (proceed with search)
						files=
				  		# else is single file from globbing
					fi
					;;
				esac
					  
				owner=${owner:-$default_owner}
				perms=${perms:-$default_mode}

				if [ "$files" != "" ]
				then
					for file in $(ls $file 2>/dev/null)
					do
						installfile "$file" "$dest" "$owner" "$perms" $parseflag || \
							exit_err $? "installing file: $dest ($file)"
					done
				else
					installfile "$file" "$dest" "$owner" "$perms" $parseflag || \
						exit_err $? "installing file: $dest ($file)"
				fi
			done
			exec 0<&9 9<&-
		    	;;

		    pkgs-remove|pkgs-postremove)
			files=$(list_install_files $stage)
			[ "$files" ] && {
				echo "  using removefiles: " $files
				remove_pkgs $(parse_installfiles $files) || exit_err $?
			}
			;;

		    pkgs-install)
			files=$(list_install_files $stage)
			[ "$files" ] && {
				echo "  using installfiles: " $files
				install_pkgs $(parse_installfiles $files) || exit_err $?
			}
			;;

		    run-install)
			script=$(filepath run-install -f -x) && {
		       		exec_script $script || exit_err $? "on script '$script'"
			}
			;;

		    purge-init)
			if $DISTRIB_HAS_UPSTART
			then
				[ -f init/files ] && {
					uninstallfiles $(cat init/files)
				}
			else
				[ -f upstart/files ] && {
					uninstallfiles $(cat upstart/files)
				}
			fi
		  esac

		done	# for stage in ...


		# marca flagfile di avvenuta esecuzione (non vale per i moduli builtin)
		#
		echo $mod | grep -q "^builtin-" || {
			[ "$LOGNAME" = "root" ] && {
				$F_EXEC && date '+%Y.%m.%d-%H:%M:%S'>$donefile
			}
		}

		$F_PROFILING && {
			echo "  [P] time:   $(date)"
			echo "  [P] dusage: $(ku-dusage /)"
			echo "  [P] diff:   $(ku-dusage diff /)"
		}

		[ -f need_reboot ] && {
			if [ -s need_reboot ]
			then
				cat need_reboot
			else
				echo -e "\n Reboot needed after this step.\n"
			fi
			rm -f need_reboot
			exit 0
		}

		cd $LIBDIR

		MODNAME= MODRUNDIR= MODLOCALDB=

	done # for mod in ...
	return 0
}



# cosmetics ...
#
printbold()
{
	local size=$1
	shift
	printf "%s* %-${size}.${size}s%s " "$KU_BOLD" "$*" "$KU_NORM"
}



update_config()
{
	local cfghost=
	local cfgurls=
	
	cfghost=$(safe_jtconf common.config_server) || exit $?
	[ "X$cfghost" = "X" ] && return 0

	decho "update_config: try to ping cfghost='$cfghost'"
	ping -w 5 -c 1 -n $cfghost >/dev/null 2>/dev/null || {
		echo "  config server '$cfghost' unreachable, remote config skipped"
		return 0
	}

	cfgurls=$(safe_jtconf common.config_urls) || exit $?
	decho "update_config: cfgurls='$cfgurls'"
	[ "X$cfgurls" = "X" ] && return 0

	is_installed wget || install_pkgs wget

	export WGET_USER=$(jtconf common.config_user 2>/dev/null)
	export WGET_PASSWORD=

	[ "$WGET_USER" != "" ] && {
		if [ -f $CONFDIR/.config_passwd ]
		then
			WGET_PASSWORD=$(cat $CONFDIR/.config_passwd)
			decho "update_config: using password stored in $CONFDIR/.config_passwd)"
		else
			stty -echo
			echo -n "enter config password: "
			read WGET_PASSWORD
			stty echo
		fi
	}

	get_and_install_config_files $cfgurls || exit $?

	unset WGET_USER WGET_PASSWORD

	return 0

} # update_config








# (MAIN)

export PATH=$(pwd)/bin:/usr/local/sbin:/sbin:/usr/sbin:/usr/local/bin:$(pwd)/modules/base/bin:$PATH

export RUNDIR="$TMPDIR/${TOOLKIT}.run"

# 2023-07-24
# first of all, can we run on this system?
#
disabled=$(kusa-conf common.disabled 2>/dev/null || :)
[ "X$disabled" != "X" ] && {
	echo -e "\naborting, reconfiguration disabled on this system: $disabled\n"
	exit 1
}

# use proxy when get remote files?
#
if getconfirm common.use_proxy
then
	[ -f /etc/profile.d/proxy.sh ] && . /etc/profile.d/proxy.sh
	export http_proxy ftp_proxy
else
	unset http_proxy
	unset ftp_proxy
fi

# globals
#
export TODAY="$(date '+%Y%m%d-%H%M%S')"
export TODAY_DATE=$(date '+%Y-%m-%d')
export TODAY_FULLDATE=$(date '+%Y-%m-%d_%H:%M')

export LOGDIR=${LOGDIR:-"/var/log/${TOOLKIT}"}
export LOGFILE=${LOGFILE:-"$LOGDIR/$TODAY.log"}
export WARNFILE=${WARNFILE:-"$LOGDIR/$TODAY.warn"}

export DONEDIR="$CONFDIR/done"
export BACKUPDIR="/etc/backup"
export JTTMPDIR="$RUNDIR"
export MODULESDIR="$RUNDIR/modules"
export SVCLIST="$RUNDIR/svclist.tmp"
export SKIPLIST="$RUNDIR/skiplist.tmp"
export MODULESLIST="$RUNDIR/modules.tmp"
export REQUIRES="$RUNDIR/requires.tmp"
export REQUIRES_POST="$RUNDIR/requires_post.tmp"
export TMPDEFS="$RUNDIR/etc/${TOOLKIT}/${TOOLKIT}.conf"
export INSTALLHISTORY="$CONFDIR/installhistory"
export ALLOWED_MODULES=

export F_EXEC=true
export F_EXEC_PRE=true
export F_BUILD_ONLY=false
export F_LIST_MODULES=false
export F_REMOVE_TEMPS=true
export F_REMOVE_SEC_CONF=true
export F_FORCE=false
export F_FORCE_CONF_DOWNLOAD=false
export F_DEBUG=false
export F_PAUSE=false
export F_PROFILING=false

export DFLAG=
export DUMMYTAG=
export XENTAG="${KU_BOLD}xen detected${KU_NORM}"

export FILES_INSTALLED=""
export FILES_HAS_INSTALLED=false
export PKGS_HAS_INSTALLED=false
export SOMETHING_CHANGED=false
export APT_NEEDS_UPDATE=true
export APT_AUTOREMOVE=
export APT_AUTOREMOVE_PREINSTALL=false
export APT_AUTOCLEAN=
export APT_AUTOINSTALL=
export APT_PARMS=
export REQ_LEVEL=0

export CHECK_FILES="$CONFDIR/svclist $CONFDIR/defines $CONFDIR/class
		$CONFDIR/conf.d/* $CONFDIR/files/* $CONFDIR/${TOOLKIT}.conf
		__BIN__/${TOOLKIT}-reconf
		__BIN__/${TOOLKIT}-functions.sh
"


# state vars
#
export KUSA_STATE_DESKTOP_RUNNING=false
export F_ABORT_NON_COMPLIANT=false

# modules sequence counters
#
export BUILD_MODULES_STEP=0
export BUILD_REQUIRES_STEP=0

# used to search files around, get here to be used
# multiple times later
#
export SEARCH_PATH=$(jtconf search_path 2>/dev/null)

# installation model (replaces old arch-* service definition)
#
export KUSA_MODEL=
KUSA_MODEL=$(jtconf model) || exit $?

# args overrides
#
f_remove_sec_conf=
f_update_config=true



# APPARMOR preliminary checks (to detect changes)

export APPARMOR_MODE="na"
export APPARMOR_MODE_CHANGED=false

$DISTRIB_HAS_APPARMOR && {
	APPARMOR_MODE=$(jtconf common.apparmor_mode 2>/dev/null || :)
	APPARMOR_MODE_CHANGED=true

	flagfile="$CONFDIR/apparmor_mode"
	prev_apparmor_mode=

	[ -s "$flagfile" ] && {
		prev_apparmor_mode=$(cat "$flagfile")
		[ "$APPARMOR_MODE" = "$prev_apparmor_mode" ] && APPARMOR_MODE_CHANGED=false
	}
	unset flagfile prev_apparmor_mode
}




#---------------------------------------------------------------------------
# library variables (classes, services, modules, etc)
#---------------------------------------------------------------------------

cd $LIBDIR

avail_classes=$(jtconf --list 'class.' | sed -e 's/class\.//')
avail_services=$(
	find modules -name svc-name -exec cat {} \; | sort -u
)
avail_modules=$(
  (
    ls modules | egrep -v '^Ubuntu|^Devuan|^builtin-'
    ls -d modules/Ubuntu*/* modules/Devuan*/* 2>/dev/null | sed -e 's#.*/##'
  ) | sort -u
)

skiplist=
[ -f $CONFDIR/skiplist ] && skiplist=$(cat $CONFDIR/skiplist)
jtconf skiplist >/dev/null 2>/dev/null && {
	skiplist="$skiplist $(jtconf skiplist)"
}

svclist=
[ -f $CONFDIR/svclist ] && svclist=$(cat $CONFDIR/svclist)
jtconf svclist >/dev/null 2>/dev/null >/dev/null && {
	svclist="$svclist $(jtconf svclist)"
}




#---------------------------------------------------------------------------
# early args check and early actions (eg: help, config files download)
#---------------------------------------------------------------------------
#
for arg
do
	case "$arg" in
	  -d|--download-conf)
		echo "  forcing config files download"
		(cd $CONFDIR; rm -f timestamp config-timestamp config-urls)
		;;
	  -h|--help)		usage ;;
	  -V|--version)		trap '' EXIT; echo "$CMDSTR"; exit 0 ;;
	  -K|--keep-sec)	f_remove_sec_conf=false ;;
	  -S|--clean-sec)	f_remove_sec_conf=true ;;
	  -k|--keep-temps)	F_REMOVE_TEMPS=false ;;
	  -D|--debug)		F_DEBUG=true ; DFLAG="--debug" ;;
	  -U|--no-update-conf)	f_update_config=false ;;
	esac
done

echo -e "\n${KU_REV} $CMD __TOOLKIT_SIGNATURE__ ${KU_NORM}\n"

# preliminary cleanup and required packages
#
[ -d $RUNDIR ] && {
	echo "error: rundir '$RUNDIR' already exists, another instance of $CMD is running," >&2
	echo "or was left from interrupted one; please remove the run directiry and try again" >&2

	# reset traps, avoid to call cleanup() on exit
	trap 1 2 3 ERR EXIT
	exit 1
}
rm -rf $RUNDIR/*
setup_directories


$f_update_config && {
	update_config || exit $?
}

# should preserve downloaded security infos?
#
case $f_remove_sec_conf in
  true|false)	decho "remove secinfos, override from commandline: $f_remove_sec_conf"
  		F_REMOVE_SEC_CONF=$f_remove_sec_conf
		;;
  *)		if getconfirm common.keep_secinfos
		then
			F_REMOVE_SEC_CONF=false
		else
			F_REMOVE_SEC_CONF=true
		fi
		decho "remove secinfos, using db setting: $F_REMOVE_SEC_CONF"
		;;
esac

for arg
do
	case $arg in
	  -u|--update-conf)	decho "pre args check, -u found, stopping"; exit 0 ;;
	esac
done

#---------------------------------------------------------------------------
# end of early args check
#---------------------------------------------------------------------------




#---------------------------------------------------------------------------
# command line args evaluation
#---------------------------------------------------------------------------


# collect profiling infos?
#
getconfirm common.profiling && F_PROFILING=true

# pause on each module?
#
case $(jtconf install.pause 2>/dev/null >/dev/null) in
	y|yes|true|1)	F_PAUSE=true ;;
	*) F_PAUSE=false ;;
esac


modules=

while [ $# != 0 ]
do
	arg=$1 ; shift
	case $arg in
		-n|--dry-run)	F_EXEC=false; F_EXEC_PRE=false; DUMMYTAG="(dummy)" ;;
		   --exec-pre)	F_EXEC=false; F_EXEC_PRE=true; DUMMYTAG="(dummy)" ;;
		-f|--force)	F_FORCE=true ;;
		-b|--build)	F_BUILD_ONLY=true; F_REMOVE_TEMPS=false ;;
		-p|--pause)	F_PAUSE=true ;;
		-m|--list-modules)
				F_LIST_MODULES=true; F_BUILD_ONLY=true; VERBOSE=false ;;
		-v|--verbose)	;;
		-x|--skip)
			[ $# = 0 ] && usage "must supply a modulename after -x\n"
			skiplist="$skiplist $1"
			shift
			;;
		-s|--service)
			[ $# = 0 ] && usage "must supply a servicename after -s\n"
			svclist="$svclist $1"
			shift
			;;
		-c|--class)
			[ $# = 0 ] && usage "must supply a class after -c\n"
			CLASS=$1
			shift
			;;
		--abort-non-compliant)
			F_ABORT_NON_COMPLIANT=true
			;;
		--skip-non-compliant)
			F_ABORT_NON_COMPLIANT=false
			;;

		# ignored, already parsed
		-h|--help) ;;
		-d|--download-conf) ;;
		-u|--update-conf) ;;
		-K|--keep-sec) ;;
		-S|--clean-sec) ;;
	  	-k|--keep-temps) ;;
		-D|--debug) ;;
		-U|--no-update-conf) ;;
		-V|--version) ;;

		-*|"")
			usage "wrong parameter: $arg\n"
			;;
		*)	modules="$modules $arg"
			;;
	esac
done

$F_DEBUG && {
	jtconf --debug common.class >/dev/null
}


#---------------------------------------------------------------------------
# sanity checks
#---------------------------------------------------------------------------

[ "$CLASS" ] || {
	[ -f $CONFDIR/class ] && CLASS=$(cat $CONFDIR/class)
}
[ "$CLASS" ] || {
	CLASS=$(jtconf common.class)
}
[ "$CLASS" ] || {
	jtconf common.autconfig_url 2>/dev/null || \
		usage "must pass a class name or define it in $CONFDIR/class\n"
}

$F_FORCE && {
	[ "$modules" ] || {
		usage "flag -f can be used only with a module list\n"
	}
}


# strips "ALL" keyword from module list
#
modules=$(echo "$modules" | sed -e 's/ *ALL *//')

# VARIUOS STATE CHECKS ----------------------------------------------------

state_check_dm_running		|| exit $?


#---------------------------------------------------------------------------
# build modules list
#---------------------------------------------------------------------------

ALLOWED_MODULES=$(jtconf common.allowed_modules 2>/dev/null || : )

# build initial modules list (with first element, builtin-start)
#
:> $MODULESLIST

# build temp skiplist file
#
:> $SKIPLIST
for i in $skiplist
do
	echo $i >>$SKIPLIST
done

# build temp svclist file
:> $SVCLIST
for i in $svclist
do
	echo $i >>$SVCLIST
done

# buildup temp defines
#
mkdir -p $(dirname $TMPDEFS) 2>/dev/null

set -- $CLASS
main_class=$1 ; shift
sub_classes=$*

echo "[common]" >> $TMPDEFS
echo "  today		$TODAY"				>>$TMPDEFS
echo "  today-date	$TODAY_DATE"			>>$TMPDEFS
echo "  today-fulldate	$TODAY_FULLDATE"		>>$TMPDEFS
echo "  kernel		$(uname -r)"			>>$TMPDEFS
echo "  arch		$(uname -m)"			>>$TMPDEFS
echo "  class		$CLASS"				>>$TMPDEFS
echo "  main_class	$main_class"			>>$TMPDEFS
echo "  sub_classes	$sub_classes"			>>$TMPDEFS
echo "  toolkit		${TOOLKIT}"			>>$TMPDEFS
echo "  version		__TOOLKIT_VERSION__"		>>$TMPDEFS
echo "  release		__TOOLKIT_RELEASE__"		>>$TMPDEFS
echo "  signature	$CMDSTR"			>>$TMPDEFS

echo -e "\n${KU_REV} summary ${KU_NORM}"
echo "
 model:              $KUSA_MODEL
 classes:            $CLASS
 services requested: "$(echo $svclist)"
 modules requested:  "$(echo $modules)"
 modules skipped:    "$(echo $skiplist)"
 apparmor_mode:      $APPARMOR_MODE (changed: $APPARMOR_MODE_CHANGED)
"

echo -e "${KU_REV} building modules list ${KU_NORM}\n"

# build module list merging required classes
# (the 'common*' classes is always added first)
#
# if a module list is passed (requested) only one passage
# for the requested list
#
build_modules "builtin-start" 1

if [ "X$modules" != "X" ]
then
	build_modules manual 1 $modules
else
	if $DISTRIB_IS_PVE
	then
		build_modules "common.pve-pre" 1
		build_modules "model-$KUSA_MODEL" 1
		build_modules "common.pve-base" 1
	else
		build_modules "common.pre" 1
		build_modules "model-$KUSA_MODEL" 1
		build_modules "common.base" 1
		build_modules "common.base2" 1
	fi

	for class in $(echo $CLASS | tr ',' ' ')
	do
		build_modules $class 1
	done

	# add optional extramodules
	#
	jtconf common.extramodules 2>/dev/null >/dev/null && {
		build_modules extra 1
	}
fi # $modules

# close modules list with common.final modules and
# builtin-cleanup module
#
if $DISTRIB_IS_PVE
then
	build_modules "pve-builtin-cleanup" 1
else
	build_modules "common.final" 1 $modules
	build_modules "builtin-cleanup" 1
fi

# search for incompatible modules
#
echo
cd $MODULESDIR
for module in $(ls)
do
	[ -f $module/incompat ] || continue
	for exclude in $(cat $module/incompat)
	do
		[ -d $exclude ] && {
			echo "  $module excluded for incompatibility with $exclude"
			rm -rf $module
			grep -v " $module$" $MODULESLIST > $MODULESLIST.tmp
			mv $MODULESLIST.tmp $MODULESLIST
			break
		}
	done
done


# sort out modules list using precedence order
#
sort -o $MODULESLIST $MODULESLIST

$F_LIST_MODULES && {
	sed -e 's/.* //' $MODULESLIST
	rm -rf $MODULESDIR
	exit 0
}

# installs distfiles definitions for modules as auto defines
#
distfile=
conffile=
basefile=


for mod in $(sed -e 's/.* //' $MODULESLIST)
do
	MODNAME=$(echo $mod | sed -e 's/^[0-9][0-9]-//')
	MODRUNDIR="$MODULESDIR/$mod"

	cd "$MODRUNDIR"
	echocr "  checking distfile: $MODNAME ... "

	for distfile in $(list_install_files dist-$mod)
	do
		basefile=$(basename "$distfile")
		conffile="$CONFDIR/conf.d/auto.00.$basefile"
		cmp "$distfile" "$conffile" >/dev/null 2>&1 || {
			echo "  instdef $conffile"
			rm -f "$conffile"
			cat "$distfile" >"$conffile" || return 1
		}
	done
	MODNAME=
	MODRUNDIR=
	echocr
done



$F_BUILD_ONLY && {
	echo -e "\nstopped - build directory is $MODULESDIR\n"
	exit 0
}

# set parms based on modules config options (after kusa db default fillup)

# apt behaviour
#
case $(jtconf install.apt_autoinstall | tr '[A-Z]' '[a-z]') in
  y|yes|1|true)	APT_AUTOINSTALL=true ;;
  n|no|0|false)	APT_AUTOINSTALL=false ;;
  ask|inter*)	APT_AUTOINSTALL='ask' ;;
  *)		exit_err 1 "install.apt_autoinstall must be true, false, or ask" ;;
esac
case $(jtconf install.apt_autoremove | tr '[A-Z]' '[a-z]') in
  y|yes|1|true)	APT_AUTOREMOVE=true ;;
  n|no|0|false)	APT_AUTOREMOVE=false ;;
  ask|inter*)	APT_AUTOREMOVE='ask' ;;
  *)		exit_err 1 "install.apt_autoremove must be true, false, or ask" ;;
esac
getconfirm install.apt_autoremove_preinstall && APT_AUTOREMOVE_PREINSTALL=true

getconfirm install.apt_autoclean	&& APT_AUTOCLEAN=true
getconfirm install.apt_allow_unauth	&& APT_PARMS="$APT_PARMS --allow-unauthenticated"


#---------------------------------------------------------------------------
# apply the configuration
#---------------------------------------------------------------------------

# and now apply the configuration (dir $MODULESDIR)
#
echo -e "\n${KU_REV} applying configuration ${KU_NORM}\n"
:> $WARNFILE
apply_config

echo -e "\nall done\n"

# cleanup and exits
#
if [ -s $WARNFILE ]
then
	echo -e "\nwarnings messages left in: $WARNFILE\n"
else
	rm -f $WARNFILE
fi

trap EXIT
cleanup
echo
echo "$CMD: all done, no errors"
echo
exit 0
