148 lines
		
	
	
		
			4.2 KiB
		
	
	
	
		
			Bash
		
	
	
		
			Executable File
		
	
	
			
		
		
	
	
			148 lines
		
	
	
		
			4.2 KiB
		
	
	
	
		
			Bash
		
	
	
		
			Executable File
		
	
	
| #!/usr/bin/env bash
 | |
| # Copyright (C) 2018  Luke Shumaker
 | |
| # Copyright (C) 2023-2024  Umorpha Systems
 | |
| # SPDX-License-Identifier: AGPL-3.0-or-later
 | |
| declare -r NAME=osi-mount
 | |
| declare -r VERSION=20231023
 | |
| 
 | |
| set -euE
 | |
| 
 | |
| install_prefix="$(realpath --logical --canonicalize-missing -- "${BASH_SOURCE[0]}/../..")"
 | |
| readonly install_prefix
 | |
| 
 | |
| source "${install_prefix}/lib/osi.sh"
 | |
| source "${install_prefix}/lib/argparse.sh"
 | |
| 
 | |
| main() {
 | |
| 	opt_specs+=(': --inside : : internal-use only')
 | |
| 	opt_visit:inside() { argparse:setmode inside; }
 | |
| 
 | |
| 	opt_specs+=(': --user : : mount the image RO, run COMMAND as SUDO_USER')
 | |
| 	opt_specs+=(': --root : : mount the image RW, run COMMAND as root, protect the rest of the system')
 | |
| 	local arg_user=
 | |
| 	opt_visit:user() { set_arg_user 'user'; }
 | |
| 	opt_visit:root() { set_arg_user 'root'; }
 | |
| 	set_arg_user() {
 | |
| 		if [[ -n $arg_user ]]; then
 | |
| 			argparse:error "Multiple --user/--root flags given"
 | |
| 		fi
 | |
| 		arg_user=$1
 | |
| 	}
 | |
| 	opt_final:root() {
 | |
| 		if [[ -z $arg_user ]]; then
 | |
| 			argparse:error "Must specify either --root or --user"
 | |
| 		fi
 | |
| 	}
 | |
| 
 | |
| 	opt_specs+=(': --rwdir : DIR : keep DIR read+write, even when --root')
 | |
| 	local arg_rwdirs=()
 | |
| 	opt_visit:rwdir() { arg_rwdirs+=("$1"); }
 | |
| 
 | |
| 	opt_specs+=('-o : --options : LIST : comma-separated list of mount options')
 | |
| 	opt_visit:options() { arg_mountopt="$1"; }
 | |
| 
 | |
| 	local arg_device=
 | |
| 	local arg_mountopt=
 | |
| 	local arg_cmd=()
 | |
| 	opt_positional() {
 | |
| 		case "$#" in
 | |
| 			0) osi.sh:error 0 "Must specify an image, mountpoint, and a command"; return;;
 | |
| 			1) osi.sh:error 0 "Must specify a mountpoint and a command"; return;;
 | |
| 			2) osi.sh:error 0 "Must specify a command"; return;;
 | |
| 		esac
 | |
| 		arg_device=$(realpath -- "$1")
 | |
| 		arg_mountpoint=$2
 | |
| 		arg_cmd=("${@:3}")
 | |
| 	}
 | |
| 
 | |
| 	local arg_orig=("$@")
 | |
| 	argparse outside "$@"
 | |
| 
 | |
| 	case "$opt_mode" in
 | |
| 		error)
 | |
| 			osi.sh:print "Try '%q --help' for more information" "${0##*/}" >&2
 | |
| 			return $EXIT_INVALIDARGUMENT
 | |
| 			;;
 | |
| 		version)
 | |
| 			osi.sh:print "%s (osi-tools) %s" "$NAME" "$VERSION"
 | |
| 			return $EXIT_SUCCESS
 | |
| 			;;
 | |
| 		help)
 | |
| 			osi.sh:print 'Usage: sudo %s [OPTIONS] --<user|root> FILENAME.img MOUNTPOINT COMMAND...' "${0##*/}"
 | |
| 			osi.sh:print 'Operating System Image: Mount'
 | |
| 			echo
 | |
| 			#     000000000011111111112222222222333333333344444444445555555555666666666677777777778
 | |
| 			#     012345678901234567890123456789012345678901234567890123456789012345678901234567890
 | |
| 			osi.sh:print 'Run COMMAND with FILENAME.img mounted at MOUNTPOINT.'
 | |
| 			echo
 | |
| 			osi.sh:print 'OPTIONS:'
 | |
| 			echo "${opt_flaghelp}" | grep -v -e --inside
 | |
| 			return $EXIT_SUCCESS
 | |
| 			;;
 | |
| 
 | |
| 		outside)
 | |
| 			osi.sh:needs_root
 | |
| 			osi.sh:needs_sudo
 | |
| 			if out="$(losetup --associated "$arg_device")" && [[ -n $out  ]]; then
 | |
| 				osi.sh:error 0 "Seems to already be mounted somewhere: %s" "$arg_device"
 | |
| 				printf '%s\n' "$out"
 | |
| 				exit $EXIT_FAILURE
 | |
| 			fi
 | |
| 
 | |
| 			# I feel guilty pulling in the hugeness of systemd-run
 | |
| 			# just for `--property=ProtectSystem=strict`, but I
 | |
| 			# don't want to implement my own protection system.
 | |
| 			flags=(
 | |
| 				--pty --same-dir --wait --collect --quiet
 | |
| 				--setenv=SUDO_UID="$SUDO_UID"
 | |
| 				${SOURCE_DATE_EPOCH:+"--setenv=SOURCE_DATE_EPOCH=$SOURCE_DATE_EPOCH"}
 | |
| 				${TMPDIR:+"--setenv=TMPDIR=$TMPDIR"}
 | |
| 				--property=PrivateMounts=true
 | |
| 			)
 | |
| 			case "$arg_user" in
 | |
| 				user) :;;
 | |
| 				root)
 | |
| 					flags+=(
 | |
| 						--property=ProtectSystem=strict
 | |
| 						--property=ProtectHome=read-only
 | |
| 						--property=ReadWritePaths="$arg_device"
 | |
| 						"${arg_rwdirs[@]/#/--property=ReadWritePaths=}"
 | |
| 					)
 | |
| 					;;
 | |
| 			esac
 | |
| 			systemd-run "${flags[@]}" -- "${BASH_SOURCE[0]}" --inside "${arg_orig[@]}"
 | |
| 
 | |
| 			if out="$(losetup --associated "$arg_device")" && [[ -n $out  ]]; then
 | |
| 				osi.sh:error 0 "umount'ed, but file is still associated with a loop device: %s" "$arg_device"
 | |
| 				printf '%s\n' "$out"
 | |
| 				exit $EXIT_FAILURE
 | |
| 			fi
 | |
| 			;;
 | |
| 		inside)
 | |
| 			osi.sh:needs_root
 | |
| 			osi.sh:needs_sudo
 | |
| 
 | |
| 			case "$arg_user" in
 | |
| 				user) arg_mountopt+="${arg_mountopt:+,}ro";;
 | |
| 				root) arg_mountopt+="${arg_mountopt:+,}rw,discard";;
 | |
| 			esac
 | |
| 
 | |
| 			cmd=(mount --no-mtab -o "$arg_mountopt" -- "$arg_device" "$arg_mountpoint")
 | |
| 			r=0
 | |
| 			out=$("${cmd[@]}" 2>&1) || r=$?
 | |
| 			if (( r != 0 )); then
 | |
| 				osi.sh:error $r '%s: %s' "${cmd[*]@Q}" "$out"
 | |
| 			fi
 | |
| 
 | |
| 			case "$arg_user" in
 | |
| 				user) osi.sh:sudo -u "#${SUDO_UID}" -- "${arg_cmd[@]}";;
 | |
| 				root) command -- "${arg_cmd[@]}";;
 | |
| 			esac
 | |
| 			;;
 | |
| 
 | |
| 		*) osi.sh:bug 'unknown opt_mode: %s' "$opt_mode";;
 | |
| 	esac
 | |
| }
 | |
| 
 | |
| main "$@"
 |