| 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. |
| 6 | # `mounter DEV|mtp://URI` mounts that one without the menu (aurora's device notifications use it). |
| 7 | |
| 8 | menu() { aurora-run -d -p "$1"; } |
| 9 | notify() { notify-send -a mounter "$@"; } |
| 10 | # Lock a LUKS device we unlocked if we bail out before mounting it. |
| 11 | cancel() { [ -n "$unlocked" ] && udisksctl lock -b "$unlocked" >/dev/null 2>&1; exit 1; } |
| 12 | die() { notify -u critical "๐พ Mount failed" "$1"; cancel; } |
| 13 | |
| 14 | # "๐พ /dev/sdb1 32G LABEL (vfat)" per unmounted filesystem; LUKS only while locked |
| 15 | # (an open one is listed as its mapper). jq instead of parsing lsblk: labels are arbitrary. |
| 16 | drives() { |
| 17 | lsblk -Jpo NAME,TYPE,FSTYPE,SIZE,LABEL,MOUNTPOINT | jq -r ' |
| 18 | .. | objects | select(has("name")) |
| 19 | | select((.type | test("^(part|disk|rom|crypt)$")) and .fstype != null and .mountpoint == null) |
| 20 | | select(.fstype | test("^(swap|LVM2_member|linux_raid_member)$") | not) |
| 21 | | select(.fstype != "crypto_LUKS" or ((.children // []) | length) == 0) |
| 22 | | (if .fstype == "crypto_LUKS" then "๐" else "๐พ" end) as $icon |
| 23 | | "\($icon) \(.name) \(.size) \(if .label then .label + " " else "" end)(\(.fstype | sub("crypto_"; "")))"' |
| 24 | } |
| 25 | |
| 26 | # "๐ฑ mtp://Google_Pixel_7_XXXX/ Pixel 7" per unmounted phone, from gvfs's volume list. |
| 27 | phones() { |
| 28 | mounted="$(gio mount -l 2>/dev/null)" |
| 29 | gio mount -li 2>/dev/null | awk ' |
| 30 | /^ *Volume\([0-9]+\): / { sub(/^ *Volume\([0-9]+\): /, ""); name = $0 } |
| 31 | /^ *activation_root=mtp:\/\// { sub(/^ *activation_root=/, ""); print $0 "\t" name }' | |
| 32 | while IFS=" " read -r uri name; do |
| 33 | printf '%s\n' "$mounted" | grep -qF -- "-> $uri" && continue |
| 34 | printf '๐ฑ %s %s\n' "$uri" "$name" |
| 35 | done |
| 36 | } |
| 37 | |
| 38 | mountphone() { |
| 39 | uri="$1" |
| 40 | err="$(gio mount "$uri" 2>&1)" || |
| 41 | die "$err |
| 42 | Unlock the phone and set USB to File transfer." |
| 43 | notify "๐ฑ Phone mounted" "${XDG_RUNTIME_DIR:-/run/user/$(id -u)}/gvfs/mtp:host=$(echo "$uri" | sed 's|^mtp://||; s|/$||')" |
| 44 | } |
| 45 | |
| 46 | mountdrive() { |
| 47 | dev="$1" |
| 48 | |
| 49 | if [ "$(lsblk -dno FSTYPE "$dev")" = "crypto_LUKS" ]; then |
| 50 | pass="$(aurora-run -d --password --lines=0 --prompt="๐" --placeholder="Passphrase for $dev" </dev/null)" || exit 1 |
| 51 | out="$(printf '%s' "$pass" | udisksctl unlock -b "$dev" --key-file /dev/stdin 2>&1)" || |
| 52 | die "Could not unlock $dev: $out" |
| 53 | unset pass |
| 54 | unlocked="$dev" |
| 55 | # "Unlocked /dev/sda2 as /dev/dm-3." |
| 56 | dev="$(echo "$out" | sed -n 's/.* as \(.*\)\.$/\1/p')" |
| 57 | udevadm settle 2>/dev/null |
| 58 | [ -n "$(lsblk -dno FSTYPE "$dev")" ] || die "$unlocked unlocked, but has no filesystem on it." |
| 59 | fi |
| 60 | |
| 61 | # "Mounted /dev/sdb1 at /run/media/k/LABEL" (older udisks add a trailing dot) |
| 62 | out="$(udisksctl mount -b "$dev" 2>&1)" || die "$out" |
| 63 | mp="$(echo "$out" | sed -n 's/^Mounted .* at \(.*\)$/\1/p' | sed 's/\.$//')" |
| 64 | |
| 65 | # Linux filesystems keep their owner: offer to take the root dir of the drive. |
| 66 | if [ -n "$mp" ] && [ ! -w "$mp" ]; then |
| 67 | owner="$(stat -c %U "$mp")" |
| 68 | [ "$(printf 'Yes\nNo' | menu "$mp is owned by $owner. Make it yours?")" = "Yes" ] && |
| 69 | pkexec chown "$(id -u):$(id -g)" "$mp" |
| 70 | fi |
| 71 | notify "๐พ Mounted" "$dev โ ${mp:-$out}" |
| 72 | } |
| 73 | |
| 74 | if [ -n "$1" ]; then |
| 75 | case "$1" in |
| 76 | mtp://*) mountphone "$1" ;; |
| 77 | *) mountdrive "$1" ;; |
| 78 | esac |
| 79 | exit 0 |
| 80 | fi |
| 81 | |
| 82 | list="$(drives; phones)" |
| 83 | [ -z "$list" ] && { notify "๐พ Mounter" "Nothing to mount."; exit 0; } |
| 84 | |
| 85 | chosen="$(echo "$list" | menu "Mount")" || exit 1 |
| 86 | target="$(echo "$chosen" | awk '{print $2}')" |
| 87 | case "$chosen" in |
| 88 | "๐ฑ"*) mountphone "$target" ;; |
| 89 | *) mountdrive "$target" ;; |
| 90 | esac |