Skip to content

Instantly share code, notes, and snippets.

@koonix
Last active July 1, 2023 18:22
Show Gist options
  • Save koonix/96640b1b85af18d209d2ba293dcbc158 to your computer and use it in GitHub Desktop.
Save koonix/96640b1b85af18d209d2ba293dcbc158 to your computer and use it in GitHub Desktop.
Automount android phones. Uses go-mtpfs.
#!/bin/sh
# this script mounts and opens any connected mtp devices while it's running.
# requires go-mtpfs, moreutils , fuse and udev.
# config
cache="${XDG_CACHE_HOME:-$HOME/.cache}/mtpd"
tmp="${XDG_RUNTIME_DIR:-/tmp}/mtpd"
main()
{
check_deps
make_dirs
udevadm monitor -ususb | while IFS= read -r dev; do
case $dev in
*' add '*|*' remove '*)
# shellcheck disable=SC2086
set -- $dev
handle "$4" &
;;
esac
done &
wait
}
handle()
{
if is_connected "$1"; then
mount "$1"
else
is_mounted "$1" && unmount_and_cleanup "$1"
fi
}
mount()
{
m="$(generate_mountpoint "$1")" || return 1
c="$(generate_cachedir "$1")" || return 1
set_mounted "$1" "$m" "$c"
TMPDIR="$c" go-mtpfs -dev=" $(get_serial "$1")$" "$m" 2>&1 |
while IFS= read -r line; do
case $line in
*mounted*) setsid -f xdg-open "$m" >/dev/null 2>&1 ;;
esac
done
is_mounted "$1" && unmount_and_cleanup "$1"
}
unmount_and_cleanup()
{
mp="$(get_mountpoint "$1")"
cd="$(get_cachedir "$1")"
unmount "$mp"
rmdir "$mp" 2>/dev/null
rm -rf "$cd"
awk -v dev="$1" '$1 != dev' "$table" | sponge "$table"
}
unmount()
{
delay=2
until fusermount -u "$1" 2>&1 | grep -Eq 'not found|(bad|invalid) mount'; do
sleep $delay
[ $delay -lt 120 ] && delay=$((delay+2))
done
}
is_connected() { [ -r "/sys/$1/serial" ] ;}
is_mounted() { cut -f1 "$table" | grep -qx "$1" ;}
set_mounted() {
( printf '%s\t%s\t%s\n' "$1" "$2" "$3"; cat "$table" ) | sponge "$table"
}
get_serial() { cat "/sys/$1/serial" ;}
get_product_name() { tr ' \t' '_' < "/sys/$1/product" ;}
get_vendor_name() { tr ' \t' '_' < "/sys/$1/manufacturer" ;}
get_mountpoint() { awk -v dev="$1" '$1 == dev {print $2; exit}' "$table" ;}
get_cachedir() { awk -v dev="$1" '$1 == dev {print $3; exit}' "$table" ;}
is_integer() { [ "$1" -eq "$1" ] 2>/dev/null ;}
generate_cachedir() { mktemp -d "$cache/XXXXXXXXXX" ;}
generate_mountpoint() {
mktemp -d "$tmp/$(get_product_name "$1")-$(get_vendor_name "$1")-XXXXXX"
}
make_dirs()
{
umask 077
mkdir -p "$cache" || die failed making dir "$cache".
mkdir -p "$tmp" || die failed making dir "$tmp".
table="$(mktemp "$tmp/mtab-XXXXXXXXXX")" ||
die failed making tmpfiles inside "$tmp".
}
check_deps()
{
for prog in go-mtpfs fusermount udevadm sponge; do
command -v $prog >/dev/null ||
die "$prog not found. \
go-mtpfs, fuse, udev and moreutils are required for mtpd."
done
pidof -q udevd || die udevd is not running.
}
die() { printf '%s\n' "mtpd: Error: $*"; exit 1 ;}
cleanup() { pkill -P$$; rm "$table"; rm -rf "${cache:?}"/* ;}
trap cleanup HUP INT TERM
main "$@"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment