#!/bin/bash

#
# Profile-sync-deamon by graysky <graysky AT archlinux DOT us>
# Inspired by some code originally written by Colin Verot
#

export BLD="\e[01m" RED="\e[01;31m" GRN="\e[01;32m" BLU="\e[01;34m" NRM="\e[00m"
VERS="5.35"

# Setup check
[[ -f /etc/psd.conf ]] && PSDCONF=${PSDCONF:-"/etc/psd.conf"}

# gentoo default
[[ -f /etc/conf.d/psd ]] && PSDCONF=${PSDCONF:-"/etc/conf.d/psd"}

if [[ ! -f $PSDCONF ]]; then
	echo -e " Cannot find $PSDCONF so bailing.	Reinstall package to use Profile-sync-daemon."	# nothing to do if there is no conf file
	exit 1
else
	. $PSDCONF
fi

BROWSERS=${BROWSERS:-"chromium conkeror.mozdev.org firefox firefox-trunk google-chrome heftig-aurora midori opera opera-next qupzilla rekonq seamonkey luakit"} # all supported browsers

[[ -z "$VOLATILE" ]] && VOLATILE="/tmp"

DAEMON_FILE=${DAEMON_FILE:-"/run/psd"}	# make it overrideable from the commandline / config

dep_check() {
	# Function is used to insure all dependencies are installed and that /etc/psd.conf is setup correctly
	command -v rsync >/dev/null 2>&1 || { echo "I require rsync but it's not installed. Aborting." >&2; exit 1; }
	command -v awk >/dev/null 2>&1 || { echo "I require awk but it's not installed. Aborting." >&2; exit 1; }

	if [[ -z "$USERS" ]]; then
		echo -e " ${BLD}Must define at least one user in ${NRM}${BLU}$PSDCONF"${NRM}	# nothing to do if there are no users
		exit 1
	fi

	for user in $(echo "$USERS"); do
		if [[ -z $(getent passwd $user) ]]; then
			# user defined a bad user in the USERS array so quit
			echo -e " ${BLD}${RED}$user${NRM}${BLD} is not a valid user on this system.  Check config file for typos: ${NRM}${BLU}$PSDCONF"${NRM}
			exit 1
		fi
	done

	for browser in $(echo "$BROWSERS"); do
		case "$browser" in
			chromium)
				return
				;;
			conkeror.mozdev.org)
				return
				;;
			firefox)
				return
				;;
			firefox-trunk)
				return
				;;
			google-chrome)
				return
				;;
			heftig-aurora)
				return
				;;
			midori)
				return
				;;
			opera)
				return
				;;
			opera-next)
				return
				;;
			qupzilla)
				return
				;;
			rekonq)
				return
				;;
			seamonkey)
				return
				;;
			laukit)
				return
				;;
			*)
				# user defined an invalid browser
				echo -e " ${BLD}${RED}$browser${NRM}${BLD} is not a supported browser.  Check config file for typos: ${NRM}${BLU}$PSDCONF"${NRM}
				exit 1
		esac
	done
}

root_check() {
	# we call this to ensure that only the root user is calling the function
	# why care? both the sync and unsync functions require root access to $DAEMON_FILE
	# Running as unprivileged user will fuck up the sync process resulting in unhappy users

	if [[ $EUID -ne 0 ]]; then
		echo -e " ${BLD}This function must be called as root!"${NRM} 1>&2
		exit 1
	fi
}

set_which() {
	### Arrays
	# profileArr is transient used to store profile paths parsed from firefox from and aurora
	# DIRArr is a full path corrected for both relative and absolute paths

	local user=$1
	local browser=$2

	homedir="$(getent passwd $user | cut -d: -f6)"
	group="$(stat -c %G $homedir)"

	# reset global variables and arrays
	unset profileArr DIRArr
	PSNAME=
	BACKUP=
	DIR=

	# skip homeless users
	if [[ -z $homedir ]]; then
		return
	fi

	case "$browser" in
		conkeror.mozdev.org)
			DIRArr[0]="$homedir/.$browser"
			PSNAME="xulrunner"
			;;
		chromium|midori)
			DIRArr[0]="$homedir/.config/$browser"
			PSNAME="$browser"
			;;
		google-chrome)
			DIRArr[0]="$homedir/.config/$browser"
			PSNAME="chrome"
			;;
		opera|opera-next)
			DIRArr[0]="$homedir/.$browser"
			PSNAME="$browser"
			;;
		qupzilla)
			#	http://blog.qupzilla.com/2013/03/qupzilla-140-released.html
			[[ -d $homedir/.$browser ]] && DIRArr[0]="$homedir/.$browser"
			[[ -d $homedir/.config/$browser ]] && DIRArr[0]="$homedir/.config/$browser"
			PSNAME="$browser"
			;;
		rekonq)
			[[ -d $homedir/.kde4/share/apps/$browser ]] && DIRArr[0]="$homedir/.kde4/share/apps/$browser"
			[[ -d $homedir/.kde/share/apps/$browser ]] && DIRArr[0]="$homedir/.kde/share/apps/$browser"
			PSNAME="$browser"
			;;
		firefox)
			if [[ -d $homedir/.mozilla/firefox ]]; then
				profileArr=( $(grep '[P,p]'ath= $homedir/.mozilla/firefox/profiles.ini|sed 's/[P,p]ath=//') )
				index=0
				PSNAME="$browser"
				for profileItem in ${profileArr[@]}; do
					if [[ $(echo $profileItem | cut -c1) = "/" ]]; then
						DIRArr[index]="$profileItem"	# path is not relative
					else
						DIRArr[index]="$homedir/.mozilla/firefox/$profileItem"	# we need to append the default path to give a fully qualified path
					fi
					index=$index+1
				done
			fi
			;;
		firefox-trunk)
			if [[ -d $homedir/.mozilla/firefox-trunk ]]; then
				profileArr=( $(grep '[P,p]'ath= $homedir/.mozilla/firefox-trunk/profiles.ini|sed 's/[P,p]ath=//') )
				index=0
				PSNAME="$browser"
				for profileItem in ${profileArr[@]}; do
					if [[ $(echo $profileItem | cut -c1) = "/" ]]; then
						DIRArr[index]="$profileItem"	# path is not relative
					else
						DIRArr[index]="$homedir/.mozilla/firefox-trunk/$profileItem"	# we need to append the default path to give a fully qualified path
					fi
					index=$index+1
				done
			fi
			;;
		heftig-aurora)
			# https://bbs.archlinux.org/viewtopic.php?id=117157
			if [[ -d $homedir/.mozilla/aurora ]]; then
				profileArr=( $(grep '[P,p]'ath= $homedir/.mozilla/aurora/profiles.ini|sed 's/[P,p]ath=//') )
				index=0
				PSNAME="aurora"
				for profileItem in ${profileArr[@]}; do
					if [[ $(echo $profileItem | cut -c1) = "/" ]]; then
						DIRArr[index]="$profileItem"	# path is not relative
					else
						DIRArr[index]="$homedir/.mozilla/aurora/$profileItem"	# we need to append the default path to give a fully qualified path
					fi
					index=$index+1
				done
			fi
			;;
		seamonkey)
			if [[ -d $homedir/.mozilla/seamonkey ]]; then
				profileArr=( $(grep '[P,p]'ath= $homedir/.mozilla/seamonkey/profiles.ini|sed 's/[P,p]ath=//') )
				index=0
				PSNAME="$browser"
				for profileItem in ${profileArr[@]}; do
					if [[ $(echo $profileItem | cut -c1) = "/" ]]; then
						DIRArr[index]="$profileItem"	# path is not relative
					else
						DIRArr[index]="$homedir/.mozilla/seamonkey/$profileItem"	# we need to append the default path to give a fully qualified path
					fi
					index=$index+1
				done
			fi
			;;
		luakit)
			DIRArr[0]="$homedir/.local/share/$browser"
			PSNAME="$browser"
			;;
		*)
			# skip invalid browser entries
			return
	esac
}

running_check() {
	# check for browsers running and refuse to start if so
	# without this cannot guarantee profile integrity
	local browser user
	for user in $USERS; do
		for browser in $BROWSERS; do
			set_which "$user" "$browser"
			if [[ -n "$PSNAME" ]] && pgrep -u "$user" "$PSNAME" &>/dev/null; then
				echo "Refusing to start; $browser is running by $user!"
				exit 1
			else
				return
			fi
		done
	done
}

dup_check() {
	# only for firefox and aurora
	# the LAST directory in the profile MUST be unique
	# make sure there are no duplicates in ~/.mozilla/<browser>/profiles.ini
	local browser user
	for user in $USERS; do
		for browser in $BROWSERS; do
			set_which "$user" "$browser"
			if [[ "$browser" = "firefox" ]] || [[ "$browser" = "firefox-trunk" ]] || [[ "$browser" = "heftig-aurora" ]] || [[ "$browser" = "seamonkey" ]]; then
				if [[ -z ${DIRArr[@]} ]]; then
					return # nothing to check
				else
					# browser is on system so check profiles
					#
					# check that the LAST DIRECTORY in the full path is unique
					unique_count=$(echo ${DIRArr[@]##*/} | sed 's/ /\n/g' | sort | uniq | wc -l)
					if [[ ${#DIRArr[@]##*/} -eq $unique_count ]]; then
						return	# no problems so do nothing
					else
						echo -e " ${RED}Error: ${NRM}${BLD}dup profile for ${GRN}$browser${NRM}${BLD} detected. See psd manpage, correct, and try again."${NRM}
						[[ "$browser" = "heftig-aurora" ]] && browser="${browser##*-}"	# clip of the 'heftig-' to give correct path
						echo -e " ${BLD}Must have unique last directories in ${BLU}$homedir/.mozilla/$browser/profiles.ini${NRM}${BLD} to use psd."${NRM}
						exit 1
					fi
				fi
			fi
		done
	done
}

kill_browsers() {
	# check for browsers running and kill them to safely sync/unsync
	# without this cannot guarantee profile integrity
	local browser user
	for user in $USERS; do
		for browser in $BROWSERS; do
			set_which "$user" "$browser"

			x=1
			while [[ $x -le 5 ]]; do
				if [[ -n "$PSNAME" ]] && pgrep -u "$user" "$PSNAME" &>/dev/null; then
					pkill -SIGTERM -u "$user" "$PSNAME"
				else
					return
				fi
				x=$(( $x + 1 ))
			done
			### Do we need a secondary, more powerful method of killing if the first fails?
		done
	done
}

parse_conf_file() {
	if [[ -z $(sed 's| \\.*$||' /etc/issue | head -n 1) ]]; then
		echo -e "${RED}Profile-sync-daemon v$VERS"${NRM}
	else
		echo -e "${RED}Profile-sync-daemon v$VERS${NRM}${BLD} on $(sed 's| \\.*$||' /etc/issue | head -n 1)."${NRM}
	fi

	echo
	echo -e "${BLD}Daemon file ${BLU}$DAEMON_FILE${NRM}${BLD} is $([[ -f $DAEMON_FILE ]] && echo present || echo not present)."${NRM}

	if [[ -f /usr/lib/systemd/system/psd.service ]]; then
		# running sysmted
		echo -e "${BLD}Service is currently $(systemctl is-active psd)."${NRM}
	fi
	echo
	echo -e "${BLD}Psd will manage the following per ${BLU}${PSDCONF}${NRM}${BLD} settings:"${NRM}
	echo
	local browser user
	for user in $USERS; do
		for browser in $BROWSERS; do
			set_which "$user" "$browser"
			for item in ${DIRArr[@]}; do
				DIR="$item"
				[[ "$browser" = "firefox" ]] || [[ "$browser" = "firefox-trunk" ]] || [[ "$browser" = "heftig-aurora" ]] || [[ "$browser" = "seamonkey" ]] && suffix="-${item##*/}" || suffix=
				psize=$(du -Lh --max-depth=0 $DIR 2>/dev/null | awk '{ print $1 }')	# profile dir size
				if [[ -d "$DIR" ]]; then
					echo -en " ${BLD}browser/psname:"
					echo -e "$(tput cr)$(tput cuf 17) $browser/$PSNAME"${NRM}
					echo -en " ${BLD}owner/group:"		
					echo -e "$(tput cr)$(tput cuf 17) $user/$group"${NRM} 
					echo -en " ${BLD}sync target:"
					echo -e "$(tput cr)$(tput cuf 17) ${BLU}$DIR"${NRM} 
					echo -en " ${BLD}tmpfs dir:"
					echo -e "$(tput cr)$(tput cuf 17) ${RED}$VOLATILE/$user-$browser$suffix"${NRM}
					echo -en " ${BLD}profile size:"
					echo -e "$(tput cr)$(tput cuf 17) $psize"${NRM} 
					echo
				fi
			done
		done
	done
}

ungraceful_state_check() {
	# if the machine was ungracefully shutdown then the backup will be on the filesystem
	# and the link to tmpfs will be on the filesystem but the contents will be empty
	# we need to simply remove the link and rotate the backup into place

	root_check
	local browser user suffix
	for user in $USERS; do
		for browser in $BROWSERS; do
			set_which "$user" "$browser"
			for item in ${DIRArr[@]}; do
				DIR="$item"
				BACKUP="$item-backup"
				[[ "$browser" = "firefox" ]] || [[ "$browser" = "firefox-trunk" ]] || [[ "$browser" = "heftig-aurora" ]] || [[ "$browser" = "seamonkey" ]] && suffix="-${item##*/}" || suffix=

				if [[ -e "$DIR/.flagged" ]]; then # all is well so continue
					return
				else
					[[ -h "$DIR" ]] && unlink "$DIR"
					[[ -d "$BACKUP" ]] && mv "$BACKUP" "$DIR"
				fi
			done
		done
	done
}

do_sync() {
	root_check
	touch "$DAEMON_FILE"

	local browser user
	for user in $USERS; do
		for browser in $BROWSERS; do
			set_which "$user" "$browser"
			for item in ${DIRArr[@]}; do
				DIR="$item"
				BACKUP="$item-backup"
				[[ "$browser" = "firefox" ]] || [[ "$browser" = "firefox-trunk" ]] || [[ "$browser" = "heftig-aurora" ]] || [[ "$browser" = "seamonkey" ]] && suffix="-${item##*/}" || suffix=

				# make tmpfs container
				if [[ -d $DIR ]]; then
					[[ -r "$VOLATILE/$user-$browser$suffix" ]] || install -dm755 --owner=$user --group=$group "$VOLATILE/$user-$browser$suffix"

					# backup target and link to tmpfs container
					if [[ $(readlink "$DIR") != "$VOLATILE/$user-$browser$suffix" ]]; then
						mv "$DIR" "$BACKUP"
					fi

					# sync the tmpfs targets to the disc
					if [[ -e $DIR/.flagged ]]; then
						rsync -aog --delete-after --delay-updates --exclude .flagged "$DIR/" "$BACKUP/"
					else
						# initial sync
						# keep user from launching browser while rsync is active
						rsync -aog --delay-updates "$BACKUP/" "$VOLATILE/$user-$browser$suffix/"
						ln -s "$VOLATILE/$user-$browser$suffix" "$DIR"
						chown -h $user:$group "$DIR"
						touch "$DIR/.flagged"
					fi
				fi
			done
		done
	done
}

do_unsync() {
	root_check
	rm -f "$DAEMON_FILE"

	local browser user
	for user in $USERS; do
		for browser in $BROWSERS; do
			set_which "$user" "$browser"
			for item in ${DIRArr[@]}; do
				DIR="$item"
				BACKUP="$item-backup"
				[[ "$browser" = "firefox" ]] || [[ "$browser" = "firefox-trunk" ]] || [[ "$browser" = "heftig-aurora" ]] || [[ "$browser" = "seamonkey" ]] && suffix="-${item##*/}" || suffix=

				# check if user has browser profile
				if [[ -h "$DIR" ]]; then
					unlink "$DIR"
					# this assumes that the backup is always updated so be sure to invoke a sync before an unsync
					#
					# restore original dirtree
					[[ -d "$BACKUP" ]] && mv "$BACKUP" "$DIR"
					[[ -d "$VOLATILE/$user-$browser$suffix" ]] && rm -rf "$VOLATILE/$user-$browser$suffix"
				fi
			done
		done
	done
}

case "$1" in
	p|P|Parse|parse|Preview|preview|debug)
		dep_check && dup_check && parse_conf_file
		;;
	sync)
		[[ ! -f $DAEMON_FILE ]] && dep_check && dup_check && running_check && ungraceful_state_check
		do_sync
		;;
	resync)
		[[ -f $DAEMON_FILE ]] && do_sync
		;;
	unsync)
		[[ -f $DAEMON_FILE ]] && do_sync && kill_browsers
		do_unsync
		;;
	*) 
		echo -e "${RED}Profile-sync-daemon v$VERS${NRM}"
		echo
		echo -e " ${BLD}$0 ${NRM}${GRN}[option]${NRM}"
		echo -e " ${BLD} ${NRM}${GRN}preview${NRM}${BLD}	Parse config file (${NRM}${BLU}${PSDCONF}${NRM}${BLD}) to see which profiles will be managed."${NRM}
		echo -e " ${BLD} ${NRM}${GRN}resync${NRM}${BLD}	Synchronize the tmpfs and media bound copy. Must be run as root user."${NRM}
		echo -e " ${BLD} ${NRM}${RED}sync${NRM}${BLD}		Force a manual sync. Must be run as root user and NOT recommended."${NRM}
		echo -e " ${BLD} ${NRM}${RED}unsync${NRM}${BLD}	Force a manual unsync. Must be run as root user and NOT recommended."${NRM}
		echo
		echo -e " ${BLD}It is ${RED}HIGHLY DISCOURAGED${NRM}${BLD} to directly call $0 to sync or to unsync."${NRM}
		if [[ -f /usr/lib/systemd/system/psd.service ]]; then
			echo -e " ${BLD}Instead, use systemd to start/stop profile-sync-daemon."${NRM}
			echo
			echo -e " ${BLD}systemctl ${NRM}${GRN}[option]${NRM}${BLD} psd psd-resync"${NRM}
			echo -e " ${BLD} ${NRM}${GRN}start${NRM}${BLD}		Turn on daemon; make symlinks and actively manage targets in tmpfs."${NRM}
			echo -e " ${BLD} ${NRM}${GRN}stop${NRM}${BLD}		Turn off daemon; remove symlinks and rotate tmpfs data back to disc."${NRM}
			echo -e " ${BLD} ${NRM}${GRN}enable${NRM}${BLD}	Autostart daemon when system comes up."${NRM}
			echo -e " ${BLD} ${NRM}${GRN}disable${NRM}${BLD}	Remove daemon from the list of autostart daemons."${NRM}
		elif [[ -f /etc/init.d/psd ]]; then
			echo -e " ${BLD}Instead, use the init system to start/stop profile-sync-daemon."${NRM}
			echo
			echo -e " ${BLD}sudo service psd ${NRM}${GRN}[option]${NRM}${BLD} or /etc/init.d/psd ${NRM}${GRN}[option]"${NRM}
			echo -e " ${BLD} ${NRM}${GRN}start${NRM}${BLD}	Turn on daemon; make symlinks and actively manage targets in tmpfs."${NRM}
			echo -e " ${BLD} ${NRM}${GRN}stop${NRM}${BLD}	Turn off daemon; remove symlinks and rotate tmpfs data back to disc."${NRM}
		fi
		;;
esac
exit 0

#vim:set ts=2 sw=2 et:
