NixOS-Config/pkgs/auto-mount/default.nix

245 lines
8.5 KiB
Nix

{
lib,
stdenv,
jq,
coreutils,
udisks,
toybox,
util-linux,
writeShellApplication,
}:
writeShellApplication
{
# Originally from: https://github.com/scawp/Steam-Deck.Mount-External-Drive/
name = "auto-mount";
runtimeInputs = [jq coreutils udisks util-linux toybox];
text = ''
set -euo pipefail
# Originally from https://serverfault.com/a/767079
# This script is called from our systemd unit file to mount or unmount
# a USB drive.
usage()
{
echo "Usage: $0 {add|remove} device_name (e.g. sdb1)"
exit 1
}
if [[ $# -ne 2 ]]; then
usage
fi
ACTION=$1
DEVBASE=$2
DEVICE="/dev/''${DEVBASE}"
# Shared between this and the auto-mount script to ensure we're not double-triggering nor automounting while formatting
# or vice-versa.
MOUNT_LOCK="/home/lillian/lock/jupiter-automount-''${DEVBASE//\/_}.lock"
# Obtain lock
exec 9<>"$MOUNT_LOCK"
if ! flock -n 9; then
echo "$MOUNT_LOCK is active: ignoring action $ACTION"
# Do not return a success exit code: it could end up putting the service in 'started' state without doing the mount
# work (further start commands will be ignored after that)
exit 1
fi
# Wait N seconds for steam
wait_steam()
{
local i=0
local wait=$1
echo "Waiting up to $wait seconds for steam to load"
while ! pgrep -x steamwebhelper &>/dev/null && (( i++ < wait )); do
sleep 1
done
}
send_steam_url()
{
local command
command="$1"
local arg
arg="$2"
local encoded
encoded=$(urlencode "$arg")
if pgrep -x "steam" > /dev/null; then
# TODO use -ifrunning and check return value - if there was a steam process and it returns -1, the message wasn't sent
# need to retry until either steam process is gone or -ifrunning returns 0, or timeout i guess
systemd-run -M 1000@ --user --collect --wait sh -c "./.steam/root/ubuntu12_32/steam steam://''${command}/''${encoded@Q}"
echo "Sent URL to steam: steam://''${command}/''${arg} (steam://''${command}/''${encoded})"
else
echo "Could not send steam URL steam://''${command}/''${arg} (steam://''${command}/''${encoded}) -- steam not running"
fi
}
# From https://gist.github.com/HazCod/da9ec610c3d50ebff7dd5e7cac76de05
urlencode()
{
[ -z "$1" ] || echo -n "$@" | hexdump -v -e '/1 "%02x"' | sed 's/\(..\)/%\1/g'
}
do_mount()
{
declare -i ret
# NOTE: these values are ABI, since they are sent to the Steam client
# shellcheck disable=SC2034
readonly FSCK_ERROR=1
# shellcheck disable=SC2034
readonly MOUNT_ERROR=2
# Get info for this drive: $ID_FS_LABEL, and $ID_FS_TYPE
dev_json=$(lsblk -o PATH,LABEL,FSTYPE --json -- "$DEVICE" | jq '.blockdevices[0]')
ID_FS_LABEL=$(jq -r '.label | select(type == "string")' <<< "$dev_json")
ID_FS_TYPE=$(jq -r '.fstype | select(type == "string")' <<< "$dev_json")
# Global mount options
OPTS="rw,noatime"
# File system type specific mount options
#if [[ ''${ID_FS_TYPE} == "vfat" ]]; then
# OPTS+=",users,gid=100,umask=000,shortname=mixed,utf8=1,flush"
#fi
case "''${ID_FS_TYPE}" in
"ntfs")
echo "FSType is NTFS"
#Extra Opts don't seem necessary anymore? add if required
#OPTS+=""
;;
"exfat")
echo "FSType is exFat"
#OPTS+=",users,gid=100,umask=000,shortname=mixed,utf8=1,flush"
;;
"btrfs")
echo "FSType is btrfs"
;;
"ext4")
echo "FSType is ext4"
#exit 2
;;
*)
echo "Error mounting ''${DEVICE}: unsupported fstype: ''${ID_FS_TYPE} - ''${dev_json}"
rm "''${MOUNT_LOCK}"
exit 2
;;
esac
# Prior to talking to udisks, we need all udev hooks (we were started by one) to finish, so we know it has knowledge
# of the drive. Our own rule starts us as a service with --no-block, so we can wait for rules to settle here
# safely.
#if ! udevadm settle; then
# echo "Failed to wait for \`udevadm settle\`"
# exit 1
#fi
# Ask udisks to auto-mount. This needs a version of udisks that supports the 'as-user' option.
ret=0
reply=$(busctl call --allow-interactive-authorization=false --expect-reply=true --json=short \
org.freedesktop.UDisks2 \
/org/freedesktop/UDisks2/block_devices/"''${DEVBASE}" \
org.freedesktop.UDisks2.Filesystem \
Mount 'a{sv}' 3 \
as-user s lillian \
auth.no_user_interaction b true \
options s "$OPTS") || ret=$?
if (( ret != 0 )); then
send_steam_url "system/devicemountresult" "''${DEVBASE}/''${MOUNT_ERROR}"
echo "Error mounting ''${DEVICE} (status = $ret)"
exit 1
fi
# Expected reply is of the format
# {"type":"s","data":["/run/media/lillian/home"]}
mount_point=$(jq -r '.data[0] | select(type == "string")' <<< "$reply" || true)
if [[ -z $mount_point ]]; then
echo "Error when mounting ''${DEVICE}: udisks returned success but could not parse reply:"
echo "---"$'\n'"$reply"$'\n'"---"
exit 1
fi
if [[ ''${ID_FS_TYPE} == "exfat" ]]; then
echo "exFat does not support symlinks, do not add library to Steam"
exit 0
fi
# Create a symlink from /run/media to keep compatibility with apps
# that use the older mount point (for SD cards only).
case "''${DEVBASE}" in
mmcblk0p*)
if [[ -z "''${ID_FS_LABEL}" ]]; then
old_mount_point="/run/media/''${DEVBASE}"
else
old_mount_point="/run/media/''${mount_point##*/}"
fi
if [[ ! -d "''${old_mount_point}" ]]; then
rm -f -- "''${old_mount_point}"
ln -s -- "''${mount_point}" "''${old_mount_point}"
fi
;;
esac
echo "**** Mounted ''${DEVICE} at ''${mount_point} ****"
if [ -f "''${mount_point}/libraryfolder.vdf" ]; then
send_steam_url "addlibraryfolder" "''${mount_point}"
else
#TODO check permissions are 1000 when creating new SteamLibrary
mkdir -p "''${mount_point}/SteamLibrary"
chown lillian:users "''${mount_point}/SteamLibrary"
send_steam_url "addlibraryfolder" "''${mount_point}/SteamLibrary"
fi
}
do_unmount()
{
local mount_point
mount_point=$(findmnt -fno TARGET "''${DEVICE}" || true)
if [[ -n $mount_point ]]; then
# Remove symlink to the mount point that we're unmounting
find /run/media -maxdepth 1 -xdev -type l -lname "''${mount_point}" -exec rm -- {} \;
else
# If we don't know the mount point then remove all broken symlinks
find /run/media -maxdepth 1 -xdev -xtype l -exec rm -- {} \;
fi
}
do_retrigger()
{
local mount_point
mount_point=$(findmnt -fno TARGET "''${DEVICE}" || true)
[[ -n $mount_point ]] || return 0
# In retrigger mode, we want to wait a bit for steam as the common pattern is starting in parallel with a retrigger
wait_steam 10
# This is a truly gnarly way to ensure steam is ready for commands.
# TODO literally anything else
sleep 6
send_steam_url "addlibraryfolder" "''${mount_point}"
}
case "''${ACTION}" in
add)
do_mount
;;
remove)
do_unmount
;;
retrigger)
do_retrigger
;;
*)
usage
;;
esac
'';
}