#!/usr/bin/env bash
# on, @ -- Run commands on another host
#
# This is like 'ssh -t <host>', but preserves the current directory.
#
#   - If called as '@' or '@foo', it will just preserve cwd exactly, i.e.
#     remote commands will act on remote files.
#     (For example, 'cd ~/Dropbox; @ ember wget ...')
#
#   - If called as 'on', it will try to access the *local* cwd via NFS,
#     i.e. remote commands will still act on local files.
#     (For example, 'cd ~src/systemd; on buildbox ninja')
#
# Accepts '@foo' symlinks (hostname in argv[0]) like ssh/rsh.
#
# Kind of inspired by the SunOS /tmp story from:
# http://web.archive.org/web/20160114202623id_/http://www-uxsup.csx.cam.ac.uk/misc/horror.txt
#
# Apparently the 'on' command did indeed exist:
# http://www.bitsavers.org/pdf/sun/sunos/3.4/800-1295-04A_Commands_Reference_Manual_198609.pdf#page=290

. lib.bash || exit
progname="on"

usage() {
	echo "Usage: on <host> [-eEKlLNrSv] [-C <path>] [<command>]"
	echo "       @ <host> [-eEKlLNrSv] [-C <path>] [<command>]"
	echo
	echo "Run an interactive shell or command on the specified host, preserving the"
	echo "current working directory. When invoked as '@' will use the remote equivalent"
	echo "of the local path; if invoked as 'on' will access the local directory via NFS."
	echo
	echo "(If the current directory is under /net/<otherhost>, -R will map it to reference"
	echo "the same location on the target system, while -L and -N will retain it as-is.)"
	echo
	echo_opt "-L"		"access local directory via NFS (default for 'on')"
	echo_opt "-r"		"map directory to remote equivalent (default for '@')"
	echo_opt "-l"		"'log in' to remote home directory (equivalent to -C.)"
	echo_opt "-C <path>"	"access specified remote directory (relative to remote home)"
	echo_opt "-N"		"keep current directory exactly (preserve /net/<thirdhost>)"
	echo
	echo_opt "-e"		"expand the command to an NFS path"
	echo_opt "-E"		"expand paths in arguments to NFS paths"
	echo
	echo_opt "-K"		"allow Kerberos ticket delegation, disable SSH multiplexing"
	echo_opt "-X"		"pass DISPLAY environment variable"
	echo_opt "-S"		"connect as superuser (root)"
	echo_opt "-v"		"verbose (display remote host and path)"
	echo
	echo "The program may also be invoked through symlinks for specific hosts:"
	echo
	echo_opt "<host>"	"like 'on -C. <host>'"
	echo_opt "@<host>"	"like 'on -R <host>' or '@ <host>'"
}

arg0=${0##*/}
wd=$PWD
nfsmode=MapLocalToTarget
host=""
user=""
Kflag=""
qflag="-q"
excomm=0
expaths=0
passdisplay=0
verbose=0

if [[ $1 == --help ]]; then
	usage; exit
elif [[ $arg0 == "on" ]]; then
	# 'on ember pwd' -- NFS-attach local directory
	nfsmode=MapLocalToNet
	if [[ $1 == - ]]; then
		# 'on - ember pwd' -- start at remote homedir
		nfsmode=MapLocalToTarget; wd="."
		shift
	fi
	if [[ $1 == [!-]* ]]; then
		host=$1; shift
	fi
elif [[ $arg0 == "@" ]]; then
	# '@ ember pwd' -- start at remote equivalent of local directory
	nfsmode=AlwaysMapToTarget
	if [[ $1 == [!-]* ]]; then
		host=$1; shift
	fi
elif [[ $arg0 == "@"?* ]]; then
	# '@ember pwd' -- same as '@'
	nfsmode=AlwaysMapToTarget
	host=${arg0#"@"}
else
	# 'ember pwd' -- start at remote homedir like regular ssh
	nfsmode=MapLocalToTarget; wd="."
	host=$arg0
fi

while getopts ":C:d:eEHKlLNrRSvX" OPT; do
	case $OPT in
	# -C is equal to -d for consistency with other tools
	C) nfsmode=MapLocalToTarget; wd=$OPTARG;;
	d) nfsmode=MapLocalToTarget; wd=$OPTARG;;
	e) excomm=1;;
	E) expaths=1;;
	H) nfsmode=MapLocalToTarget; wd=".";;	# obsolete, renamed to -l
	K) Kflag="-K -Snone";;
	l) nfsmode=MapLocalToTarget; wd=".";;	# 'login' mode, goes to remote home
	L) nfsmode=MapLocalToNet;;		# default 'on' behavior
	N) nfsmode=MapLocalToTarget;;
	r) nfsmode=AlwaysMapToTarget;;		# default '@' behavior
	R) nfsmode=AlwaysMapToTarget;;		# obsolete, renamed to -r
	S) user="root@";;
	v) verbose=1;;
	X) passdisplay=1;;
	*) lib:die_getopts;;
	esac
done; shift $((OPTIND-1))

if [[ ! $host ]]; then
	host=$1; shift
	if [[ ! $host ]]; then
		vdie "host not specified"
	fi
fi

if ! klist -s; then
	vmsg "no Kerberos tickets"
	qflag=""
fi

# Translate the local working directory

case $nfsmode in
	MapLocalToNet)
		# 'on <host>' mode (-L)
		#
		# Always add /net/$CLIENT (unless already on /net/$OTHER)
		# in order to stay wherever the client's working directory is.

		if [[ $wd == /net/* ]]; then
			# NFS mount - strip if it's for target, keep as-is otherwise
			shopt -s extglob
			wd=${wd%/}/
			wd=/${wd#"/net/$host/"}
			wd=${wd%/}
			wd=/${wd##+(/)}
			shopt -u extglob
		elif [[ $wd == /* ]]; then
			# Local path - convert to NFS mount
			wd="/net/$HOSTNAME$wd"
			wd=${wd%/}
			if [[ ! -e /proc/fs/nfsd/versions ]]; then
				die "NFS service not running on $HOSTNAME"
			fi
		else
			# Semantics undecided
			lib:crash "-L with relative paths not implemented"
		fi
		;;
	MapLocalToTarget)
		# Partial-'@' mode (-N), also implied by '-C <path>'
		#
		# Keep the path intact (map local 1:1 to target; /net/$OTHER
		# stays as-is). Only strip /net/$TARGET to make the path nicer
		# without functional change.

		if [[ $wd == /* ]]; then
			shopt -s extglob
			wd=${wd%/}/
			wd=/${wd#"/net/$host/"}
			wd=${wd%/}
			wd=/${wd##+(/)}
			shopt -u extglob
		fi
		;;
	AlwaysMapToTarget)
		# '@<host>' mode (-R), also '<host>' (-D)
		#
		# Always strip /net/* (including /net/$OTHER) and access the
		# target host.
		#
		# Relative paths remain as-is and are assumed to already be
		# valid on the target. (In particular, "." used by '<host>'
		# aliases should continue to refer to the remote homedir.)

		if [[ $wd == /* ]]; then
			shopt -s extglob
			wd=${wd%/}/
			wd=/${wd#/net/*/}
			wd=${wd%/}
			wd=/${wd##+(/)}
			shopt -u extglob
		fi
		;;
	*)
		lib:crash "bad nfsmode value"
esac

if (( TEST_ON )); then
	echo "$wd"; exit
fi

if (( verbose )); then
	vmsg "Running in \"$host:${wd:-~}\""
fi

if [[ $wd ]]; then
	q_wd=${wd@Q}
else
	q_wd=""
fi

# Optionally expand the command and/or any ./relative paths to NFS paths

if (( $# == 0 )); then
	q_cmd='$SHELL'
elif (( $# == 1 )) && [[ $1 == *\ * ]]; then
	if (( excomm || expaths )); then
		vdie "cannot expand paths in a quoted shell command"
	fi
	q_cmd=$1
else
	if (( excomm )); then
		arg=$1; shift
		arg=$(which "$arg") || exit
		if [[ $arg == /* && $arg != /@(afs|n|net)/* ]]; then
			arg="/net/$HOSTNAME$arg"
		fi
		if (( verbose )); then
			vmsg "Using \"$arg\" as remote command"
		fi
		set -- "$arg" "$@"
	fi
	if (( expaths )); then
		args=()
		for arg; do
			if [[ $arg == ./* ]]; then
				arg=$(realpath -s "$arg")
			fi
			if [[ $arg == /* && $arg != /@(afs|n|net)/* ]]; then
				arg="/net/$HOSTNAME$arg"
			fi
			debug "Expanding argument to \"$arg\""
			args+=("$arg")
		done
		set -- "${args[@]}"
	fi
	q_cmd=${@@Q}
fi

if (( passdisplay )); then
	if [[ $DISPLAY == *?:* ]]; then
		q_cmd="export DISPLAY=${DISPLAY@Q}; $q_cmd"
	elif [[ $DISPLAY ]]; then
		# XXX: Should just set ssh -Y?
		vdie "cannot pass a local display address ($DISPLAY) currently"
	else
		vdie "no display address to pass"
	fi
fi

if [[ -t 0 && -t 1 && -t 2 ]]; then
	tflag="-t"
else
	tflag=""
fi

ssh $Kflag $qflag $tflag "$user$host" \
	"export SILENT=1;
	if [ -e /etc/profile ]; then . /etc/profile; fi;
	if [ -e ~/.profile ]; then . ~/.profile; fi;
	cd $q_wd && ($q_cmd)"
