Problem
When it comes to security I like to don a tin foil hat. As such I use the, off-line password manager, Keepass. Keepass allows you to to use multi-factor authentication via; a password, a keyfile and the Windows user account. I enabled only two of these, the password and the keyfile. Because I have two computers that need access to the keyfile I've stored it on a USB stick.
On Windows everything is fine, I plug the USB in, I open the database, I eject the USB from the tray, remove USB and I can use the passwords I need. This in all takes two seconds.
Solution
However on Linux this feels like a whole song and dance. Consisting of something similar to the following:
# Plug in USB lsblk sudo mount /dev/usb1 /path/to/folder keypass & sudo umount /dev/usb1 udisksctl power-off -b /dev/usb lsblk # Remove USB
Whilst this in theory isn't much different than Windows, it's just an added mount and unmount.
Having to check lsblk
and type all of that just to get a password is inconvenient.
And so I decided to script this away, it's just ~6 commands, how hard can it be?
Fishy Solution
I'm a beginner with fish, I've not used functions or lists before.
And the only time I've set something was to update my path.
I'm such a noob that I tried to return ("" "")
to return a list... 🤦
However I have some experience in other, non-shell, languages and feel I've picked it up ok.
pdev
- finds a partition's UUID, pkname, block path and mountpoint all from the UUID. If a device with a matching UUID is not found then it will continuously inform the user and attempt to reacquire the data.pmount
- This takes four positional arguments for the partition; UUID, target mountpoint, block path, current mountpoint.- If the partition has a current mountpoint it will fail if this is not the target mountpoint.
- Otherwise, if the block path is a value then it will mount the partition to the target path.
- Finally if neither of these are true it does nothing.
umount_all
- This is given a pkname and unmounts all partitions of the drive. Whilst it's unlikely that that the drive will have multiple partitions, and it's even more unlikely that they'll be mounted. I would prefer to err on the side of caution.load_passwords
- This is effectively the 'main' function. It takes a UUID and a target path. From here it:- Get the mount information from
pdev
. - Get the drive's path. (This would be
/dev/usb
rather than/dev/usb1
) This useslsblk
as simply cutting off the last number wouldn't work on some storage devices. (I.e. nvme0n1p1) - Display the partition's information, the block path, highlighting the drive's path, and the current mountpoint.
- Attempt to mount the drive using
pmount
. - Open keypass.
- Ask the user if they wish to eject the drive.
- Exit successfully if the user does not wish to eject the drive.
- Unmount all the partitions on the drive with
umount_all
. - Power off the drive.
- Verify the device has been powered off successfully.
- Get the mount information from
passwords
- A convenience function to pass the UUID and mountpoint that I use.
function pdev
while true
set mounts (lsblk -l -o UUID,PKNAME,PATH,MOUNTPOINT | grep "^$argv[1] " | grep -Po "[^ ]+")
if set -q mounts[1]
break
end
read -P "Insert key drive "
end
for mount in $mounts
echo $mount
end
end
function pmount
if set -q argv[4]
if test $argv[4] != $argv[2]
echo "Mounted to wrong directory"
return 1
end
else if set -q argv[3]
sudo mount UUID=$argv[1] $argv[2]
end
end
function umount_all
set blocks (lsblk -l -o PKNAME,PATH,MOUNTPOINT | grep "^$argv[1] ")
for block_ in $blocks
set block (echo "$block_" | grep -Po "/[^ ]+")
if set -q block[2]
sudo umount "$block[1]"
end
end
end
function load_passwords
set mounts (pdev $argv[1])
if test $status != 0
return 1
end
set drive (lsblk -o NAME,PATH | grep "^$mounts[2] " | grep -Po "/[^ ]+")
echo "PATH : $mounts[3]" | grep "$drive"
echo "MOUNT: $mounts[4]"
pmount $argv[1] $argv[2] $mounts[3] $mounts[4]
if test $status != 0
return 1
end
keepass &
read -P "Eject drive? [Y/n] " -l input
echo "$input" | grep -Poi "(^\$)|(^y)" >> /dev/null
if test $status = 1
return
end
umount_all "$mounts[2]"
udisksctl power-off -b $drive
lsblk -o UUID | grep "^$argv[1]\$" >> /dev/null
if test $status = 1
return
end
echo "Failed to power off drive"
return 1
end
function passwords
load_passwords {redacted} /path/to/mountpoint
end
Concerns
If not a fan of using a for loop to echo each value in a list to 'return' a list. Is there a cleaner way to do this?
for mount in $mounts echo $mount end
I'm not a fan of names like
$argv[1]
rather than$uuid
as they make the code harder to understand. Is there a clean way to specify these?The code feels unreadable, it's why I've written such a thorough description here. I can see myself forgetting all this nuance and coming back to this in a year and go, which idiot wrote this?! 😡
I'm not a fan of having all the functions be public,
pmount
should probably be private.I'm not a fan of needing
sudo
when I have access to/path/to/mountpoint
. There's a certain irony to needing to enter two passwords to get one...I feel the code is just kinda messy and not great.
I am also happy for any other comments on my code.