j0h

autoUSBmountHeadless

j0h
Feb 3rd, 2026
181
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Bash 4.75 KB | None | 0 0
  1. #!/usr/bin/env bash
  2. set -euo pipefail
  3.  
  4. # Fixed mountpoint (login-independent)
  5. MOUNTPOINT="/media/mcp/device"
  6.  
  7. # Optional: if set, only mount a filesystem with this label (leave empty to accept any)
  8. WANTED_LABEL=""
  9.  
  10. # Optional: for vfat/exfat/ntfs, force ownership to this uid:gid
  11. OWNER_USER="mcp"
  12. OWNER_GROUP="mcp"
  13.  
  14. LOG_TAG="usb-auto-mount"
  15.  
  16. log() {
  17.   logger -t "$LOG_TAG" -- "$*"
  18.   echo "[$(date '+%F %T')] $*"
  19. }
  20.  
  21. ensure_mountpoint() {
  22.   mkdir -p "$MOUNTPOINT"
  23. }
  24.  
  25. uid_of() { id -u "$OWNER_USER"; }
  26. gid_of() { id -g "$OWNER_GROUP"; }
  27.  
  28. # Return 0 if MOUNTPOINT is currently mounted
  29. is_mounted() {
  30.   findmnt -rno TARGET "$MOUNTPOINT" >/dev/null 2>&1
  31. }
  32.  
  33. # If mounted, unmount it
  34. do_unmount() {
  35.   if is_mounted; then
  36.     local src
  37.     src="$(findmnt -rno SOURCE "$MOUNTPOINT" || true)"
  38.     log "Unmounting $MOUNTPOINT (source: ${src:-unknown})"
  39.     umount -l "$MOUNTPOINT" || umount "$MOUNTPOINT" || true
  40.   fi
  41. }
  42.  
  43. # Pick a USB partition device node to mount, e.g. /dev/sda1
  44. # Strategy:
  45. #  1) look at /dev/disk/by-id/usb-* symlinks
  46. #  2) resolve to kernel device
  47. #  3) choose a PARTITION if present; else use whole-disk block if it has a filesystem
  48. pick_usb_partition() {
  49.   local byid dev kname part
  50.  
  51.   # Prefer stable USB by-id names
  52.   for byid in /dev/disk/by-id/usb-*; do
  53.     [[ -e "$byid" ]] || continue
  54.     dev="$(readlink -f "$byid" || true)"
  55.     [[ -b "$dev" ]] || continue
  56.  
  57.     # Get kernel name (e.g. sda, sdb)
  58.     kname="$(lsblk -no KNAME "$dev" 2>/dev/null | head -n1 || true)"
  59.     [[ -n "$kname" ]] || continue
  60.  
  61.     # Prefer first partition on that disk (e.g. /dev/sda1)
  62.     part="/dev/${kname}1"
  63.     if [[ -b "$part" ]]; then
  64.       echo "$part"
  65.       return 0
  66.     fi
  67.  
  68.     # Otherwise, if the whole device has a filesystem, allow it (rare but possible)
  69.     if lsblk -no FSTYPE "$dev" 2>/dev/null | grep -qE '.+'; then
  70.       echo "$dev"
  71.       return 0
  72.     fi
  73.   done
  74.  
  75.   return 1
  76. }
  77.  
  78. # Get filesystem type and label
  79. fstype_of() { lsblk -no FSTYPE "$1" 2>/dev/null | head -n1 || true; }
  80. label_of()  { lsblk -no LABEL  "$1" 2>/dev/null | head -n1 || true; }
  81.  
  82. # Mount with sane options per filesystem
  83. do_mount() {
  84.   local dev="$1"
  85.   local fstype label opts uid gid
  86.  
  87.   fstype="$(fstype_of "$dev")"
  88.   label="$(label_of "$dev")"
  89.  
  90.   if [[ -z "$fstype" ]]; then
  91.     log "Device $dev has no detectable filesystem (skipping)"
  92.     return 1
  93.   fi
  94.  
  95.   if [[ -n "$WANTED_LABEL" && "$label" != "$WANTED_LABEL" ]]; then
  96.     log "Device $dev label '$label' != wanted '$WANTED_LABEL' (skipping)"
  97.     return 1
  98.   fi
  99.  
  100.   uid="$(uid_of)"
  101.   gid="$(gid_of)"
  102.  
  103.   # Default mount options
  104.   opts="rw,nosuid,nodev,noatime"
  105.  
  106.   # For non-POSIX fs, set ownership/masks so mcp can write
  107.   case "$fstype" in
  108.     vfat|fat|msdos)
  109.       opts="$opts,uid=$uid,gid=$gid,fmask=0022,dmask=0002,utf8=1"
  110.       ;;
  111.     exfat)
  112.       opts="$opts,uid=$uid,gid=$gid,fmask=0022,dmask=0002"
  113.       ;;
  114.     ntfs|ntfs3)
  115.       opts="$opts,uid=$uid,gid=$gid,umask=0002"
  116.       ;;
  117.     ext4|ext3|ext2|xfs|btrfs)
  118.       # ownership handled by filesystem permissions
  119.       ;;
  120.     *)
  121.       log "Filesystem type '$fstype' on $dev is unhandled; trying generic mount"
  122.       ;;
  123.   esac
  124.  
  125.   log "Mounting $dev (fstype=$fstype label='${label:-}' ) -> $MOUNTPOINT"
  126.   mount -t "$fstype" -o "$opts" "$dev" "$MOUNTPOINT"
  127. }
  128.  
  129. # Try to mount if nothing is mounted
  130. try_mount_once() {
  131.   ensure_mountpoint
  132.  
  133.   if is_mounted; then
  134.     return 0
  135.   fi
  136.  
  137.   local dev=""
  138.   if dev="$(pick_usb_partition)"; then
  139.     do_mount "$dev" || true
  140.   else
  141.     # No USB devices currently visible
  142.     :
  143.   fi
  144. }
  145.  
  146. main() {
  147.   ensure_mountpoint
  148.  
  149.   # Serialize (avoid multiple instances fighting)
  150.   exec 9>/run/usb-auto-mount.lock
  151.   if ! flock -n 9; then
  152.     log "Another instance is running; exiting."
  153.     exit 0
  154.   fi
  155.  
  156.   # Initial attempt in case USB is already plugged in
  157.   try_mount_once
  158.  
  159.   log "Watching udev for USB block add/remove events..."
  160.  
  161.   # Monitor udev events for block devices.
  162.   # We'll react to add/remove and re-check state each time.
  163.   udevadm monitor --udev --subsystem-match=block | while IFS= read -r line; do
  164.     # We react on lines that contain "add" or "remove"
  165.     if grep -qE '\b(add|remove)\b' <<<"$line"; then
  166.       # Small settle to let partitions appear/disappear
  167.       udevadm settle --timeout=5 || true
  168.  
  169.       # If current mount source disappeared, unmount
  170.       if is_mounted; then
  171.         local src
  172.         src="$(findmnt -rno SOURCE "$MOUNTPOINT" || true)"
  173.         if [[ -n "$src" && ! -b "$src" ]]; then
  174.           log "Mounted source $src no longer exists; unmounting."
  175.           do_unmount
  176.         fi
  177.       fi
  178.  
  179.       # If not mounted, try mounting
  180.       try_mount_once
  181.     fi
  182.   done
  183. }
  184.  
  185. trap 'log "Shutting down; unmounting."; do_unmount' INT TERM
  186. main
  187.  
Advertisement
Add Comment
Please, Sign In to add comment