| @@ -1,41 +1,81 @@ |
| 1 | 1 | | #!/bin/sh |
| 2 | + | # Menu to mount drives, LUKS partitions and Android phones, the standard way: |
| 3 | + | # udisks mounts drives to /run/media/$USER/<label> (fstab entries to their fstab |
| 4 | + | # target) and unlocks LUKS with a passphrase asked in aurora's runner; phones go |
| 5 | + | # through gvfs (/run/user/$UID/gvfs/mtp:host=...). Root is asked via polkit. See unmounter. |
| 2 | 6 | | |
| 3 | | − | # Gives a dmenu prompt to mount unmounted drives. If |
| 4 | | − | # they're in /etc/fstab, they'll be mounted automatically. Otherwise, you'll |
| 5 | | − | # be prompted to give a mountpoint from already existsing directories. If you |
| 6 | | − | # input a novel directory, it will prompt you to create that directory. |
| 7 | | − | |
| 8 | | − | getmount() { \ |
| 9 | | − | [ -z "$chosen" ] && exit 1 |
| 10 | | − | # shellcheck disable=SC2086 |
| 11 | | − | mp="$(find $1 2>/dev/null | aurora-run -d -p "Type in mount point.")" || exit 1 |
| 12 | | − | test -z "$mp" && exit 1 |
| 13 | | − | if [ ! -d "$mp" ]; then |
| 14 | | − | mkdiryn=$(printf "No\\nYes" | aurora-run -d -p "$mp does not exist. Create it?") || exit 1 |
| 15 | | − | [ "$mkdiryn" = "Yes" ] && (mkdir -p "$mp" || sudo -A mkdir -p "$mp") |
| 7 | + | menu() { aurora-run -d -p "$1"; } |
| 8 | + | notify() { notify-send -a mounter "$@"; } |
| 9 | + | # Lock a LUKS device we unlocked if we bail out before mounting it. |
| 10 | + | cancel() { [ -n "$unlocked" ] && udisksctl lock -b "$unlocked" >/dev/null 2>&1; exit 1; } |
| 11 | + | die() { notify -u critical "💾 Mount failed" "$1"; cancel; } |
| 12 | + | |
| 13 | + | # "💾 /dev/sdb1 32G LABEL (vfat)" per unmounted filesystem; LUKS only while locked |
| 14 | + | # (an open one is listed as its mapper). jq instead of parsing lsblk: labels are arbitrary. |
| 15 | + | drives() { |
| 16 | + | lsblk -Jpo NAME,TYPE,FSTYPE,SIZE,LABEL,MOUNTPOINT | jq -r ' |
| 17 | + | .. | objects | select(has("name")) |
| 18 | + | | select((.type | test("^(part|disk|rom|crypt)$")) and .fstype != null and .mountpoint == null) |
| 19 | + | | select(.fstype | test("^(swap|LVM2_member|linux_raid_member)$") | not) |
| 20 | + | | select(.fstype != "crypto_LUKS" or ((.children // []) | length) == 0) |
| 21 | + | | (if .fstype == "crypto_LUKS" then "🔒" else "💾" end) as $icon |
| 22 | + | | "\($icon) \(.name) \(.size) \(if .label then .label + " " else "" end)(\(.fstype | sub("crypto_"; "")))"' |
| 23 | + | } |
| 24 | + | |
| 25 | + | # "📱 mtp://Google_Pixel_7_XXXX/ Pixel 7" per unmounted phone, from gvfs's volume list. |
| 26 | + | phones() { |
| 27 | + | mounted="$(gio mount -l 2>/dev/null)" |
| 28 | + | gio mount -li 2>/dev/null | awk ' |
| 29 | + | /^ *Volume\([0-9]+\): / { sub(/^ *Volume\([0-9]+\): /, ""); name = $0 } |
| 30 | + | /^ *activation_root=mtp:\/\// { sub(/^ *activation_root=/, ""); print $0 "\t" name }' | |
| 31 | + | while IFS=" " read -r uri name; do |
| 32 | + | printf '%s\n' "$mounted" | grep -qF -- "-> $uri" && continue |
| 33 | + | printf '📱 %s %s\n' "$uri" "$name" |
| 34 | + | done |
| 35 | + | } |
| 36 | + | |
| 37 | + | mountphone() { |
| 38 | + | uri="$1" |
| 39 | + | err="$(gio mount "$uri" 2>&1)" || |
| 40 | + | die "$err |
| 41 | + | Unlock the phone and set USB to File transfer." |
| 42 | + | notify "📱 Phone mounted" "${XDG_RUNTIME_DIR:-/run/user/$(id -u)}/gvfs/mtp:host=$(echo "$uri" | sed 's|^mtp://||; s|/$||')" |
| 43 | + | } |
| 44 | + | |
| 45 | + | mountdrive() { |
| 46 | + | dev="$1" |
| 47 | + | |
| 48 | + | if [ "$(lsblk -dno FSTYPE "$dev")" = "crypto_LUKS" ]; then |
| 49 | + | pass="$(aurora-run -d --password --lines=0 --prompt="🔒" --placeholder="Passphrase for $dev" </dev/null)" || exit 1 |
| 50 | + | out="$(printf '%s' "$pass" | udisksctl unlock -b "$dev" --key-file /dev/stdin 2>&1)" || |
| 51 | + | die "Could not unlock $dev: $out" |
| 52 | + | unset pass |
| 53 | + | unlocked="$dev" |
| 54 | + | # "Unlocked /dev/sda2 as /dev/dm-3." |
| 55 | + | dev="$(echo "$out" | sed -n 's/.* as \(.*\)\.$/\1/p')" |
| 56 | + | udevadm settle 2>/dev/null |
| 57 | + | [ -n "$(lsblk -dno FSTYPE "$dev")" ] || die "$unlocked unlocked, but has no filesystem on it." |
| 16 | 58 | | fi |
| 17 | | − | } |
| 18 | | − | |
| 19 | | − | mountusb() { \ |
| 20 | | − | chosen="$(echo "$usbdrives" | aurora-run -d -p "Mount which drive?")" || exit 1 |
| 21 | | − | chosen="$(echo "$chosen" | awk '{print $1}')" |
| 22 | | − | sudo -A mount "$chosen" 2>/dev/null && notify-send "💻 USB mounting" "$chosen mounted." && exit 0 |
| 23 | | − | alreadymounted=$(lsblk -nrpo "name,type,mountpoint" | awk '$3!~/\/boot|\/home$|SWAP/&&length($3)>1{printf "-not ( -path *%s -prune ) ",$3}') |
| 24 | | − | getmount "/mnt /media /mount /home -maxdepth 5 -type d $alreadymounted" |
| 25 | | − | partitiontype="$(lsblk -no "fstype" "$chosen")" |
| 26 | | − | case "$partitiontype" in |
| 27 | | − | "vfat") sudo -A mount -t vfat "$chosen" "$mp" -o rw,umask=0000;; |
| 28 | | − | "exfat") sudo -A mount "$chosen" "$mp" -o uid="$(id -u)",gid="$(id -g)";; |
| 29 | | − | *) sudo -A mount "$chosen" "$mp"; user="$(whoami)"; ug="$(groups | awk '{print $1}')"; sudo -A chown "$user":"$ug" "$mp";; |
| 30 | | − | esac |
| 31 | | − | notify-send "💻 USB mounting" "$chosen mounted to $mp." |
| 32 | | − | } |
| 33 | | − | |
| 34 | | − | usbdrives="$(lsblk -rpo "name,type,size,mountpoint" | grep 'part\|rom' | awk '$4==""{printf "%s (%s)\n",$1,$3}')" |
| 35 | | − | |
| 36 | | − | if [ -z "$usbdrives" ]; then |
| 37 | | − | echo "No USB drive detected" && exit |
| 38 | | − | else |
| 39 | | − | echo "USB drive(s) detected." |
| 40 | | − | mountusb |
| 41 | | − | fi |
| 59 | + | |
| 60 | + | # "Mounted /dev/sdb1 at /run/media/k/LABEL" (older udisks add a trailing dot) |
| 61 | + | out="$(udisksctl mount -b "$dev" 2>&1)" || die "$out" |
| 62 | + | mp="$(echo "$out" | sed -n 's/^Mounted .* at \(.*\)$/\1/p' | sed 's/\.$//')" |
| 63 | + | |
| 64 | + | # Linux filesystems keep their owner: offer to take the root dir of the drive. |
| 65 | + | if [ -n "$mp" ] && [ ! -w "$mp" ]; then |
| 66 | + | owner="$(stat -c %U "$mp")" |
| 67 | + | [ "$(printf 'Yes\nNo' | menu "$mp is owned by $owner. Make it yours?")" = "Yes" ] && |
| 68 | + | pkexec chown "$(id -u):$(id -g)" "$mp" |
| 69 | + | fi |
| 70 | + | notify "💾 Mounted" "$dev → ${mp:-$out}" |
| 71 | + | } |
| 72 | + | |
| 73 | + | list="$(drives; phones)" |
| 74 | + | [ -z "$list" ] && { notify "💾 Mounter" "Nothing to mount."; exit 0; } |
| 75 | + | |
| 76 | + | chosen="$(echo "$list" | menu "Mount")" || exit 1 |
| 77 | + | target="$(echo "$chosen" | awk '{print $2}')" |
| 78 | + | case "$chosen" in |
| 79 | + | "📱"*) mountphone "$target" ;; |
| 80 | + | *) mountdrive "$target" ;; |
| 81 | + | esac |