initial commit

master
Anton Lydike 2 years ago
commit 2603138534

@ -0,0 +1,3 @@
# mounts file, read by the mnt script. See README for more info.
# columns are tab separated
# blockdevice local path where it is usually mounted to command to mount command to unmount name

@ -0,0 +1,7 @@
Copyright © 2022 Anton Lydike
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

@ -0,0 +1,85 @@
# mnt - A mount/unmount utility for the fish shell
`mnt` wants to be the fastest way to mount/unmount external media from the command line. You can configure custom mount commands, it has autocompletion and some user-friendly matching logic.
## Install
Run the `install.fish` script to install `mnt.fish` into your fish functions (and completions). It install all dependencies as well, which are the `coalesce` and `shorten_path` fish functions, they are:
* `coalesce ARGS...` prints the first non-empty argument.
* `shorten_path /path/to/somewhere` shortens all path segments except the last one to be one character, smilar to fishs prompt.
The install script expects your fish config folder to reside in `~/.config/fish/`. If it cant find `~/.config/fish/config.fish` it will abort the installation. You can either pass the path to your fish config to the install script, or install it manually. The install script symlinks the files from this repo into your fish config folder, which allows for easy updating using `git pull` in this repo. If you don't want that, you can specify the `--copy` flag to the install script to copy the files instead.
### External Dependencies
This script uses `ripgrep`, `blkid`, `jq` and `udisksctl`. Make sure they are installed on your system, or you'll get weird errors!
### Extra Scripts
This repo also contains two more scripts, one named `mount-luks` and one called `mount-vc`. They:
- `mount-luks DEVICE PASS_KEY MAPPER_NAME` mount a luks-encrypted volume using a password from the pass password manager (unmount using `mount-luks -u MAPPER_NAME`)
- mount a veracrypt volume using either a keyfile, or a password. Note that since the password is passed as an argument to the veracrypt process, it can be observed by other processes until the veracrypt process terminates (I am not exactly sure how long that is though, probably only until it finished mounting, but it could be longer.)
You can copy and modify these scripts as needed.
## Usage
Run `mnt` to get a status listing for all mounts (Same as `mnt -l`). Example output:
```
> mnt
[-] /d/d/b/4dfea5ae-d72a-4c9c-b3e5-5695f6ac40a0 NVME USB
[m] /d/s/r/m/a/writable (54,8G)
[u] /d/sdc1 ??? (3,1G)
[u] /d/sdc2 ??? (3,9M)
```
The status of each mount is indicated by the color and symbol in front:
- `[-]` (red) - This volume is currently unavailable, it cannot be mounted
- `[u]` (yellow) - This volume is unmounted, you can mount it
- `[m]` (green) - This volume is mounted, you can unmount it
It also prints the device, the devices label, if available (otherwise `???`) and the devices size (if it can be determined).
You can mount a volume by simply specifying `mnt IDENTIFIER`, it will autocomplete available block devices for you, but you can also specify (parts of) labels or size, basically anything that identifies the volume uniquely. (The volume is selected by grepping for `IDENTIFIER` in the output of `mnt_core_list_mounts`)
Unmounting is just as easy, you can run `mnt -u IDENTIFIER`, with the same rules for the identifier. The autocompletion will only offer you already mounted devices.
### Configuration
The script mounts block devices using `udisksctl` by default. If you have a more involved setup, you can edit the `.mounts` file in your home directory. The format is roughly documented in the file, but I'll go into more detail here:
The file contains five columns, separated by tabs (real tab characters). Empty lines, or lines starting with a `#` are skipped.
The columns are:
- block device
- path where it would mount to
- command to mount (can be a fish function, can use arguments, just be careful with tab characters. You currently cannot escape them in the config file). the variables `$device` and `$path` can be used here, they will be replaced with the block device, and mount path respectively.
- command to unmount (see above)
- display name
You can use the `mount-vc` and `mount-luks` scripts here for ease of use. e.g.
```
> cat .mounts
# my luks-encrypted USB
/dev/disk/by-uuid/<uuid> /run/media/<user>/<id> mount-luks $device devices/my_luks_usb luks_usb mount-luks -u luks_usb My Luks USB
# my veracrypt HDD using a key in ~/.keys
/dev/disk/by-uuid/<uuid> /run/media/<user>/<id> mount-vc $device my-hdd-key $path mount-vc -u $path Veracrypt HDD
```
You can then mount/unmount these using `mnt Luks` or `mnt HDD` (because of the way the `IDENTIFIER` is resolved to an entry in the volume table).
## TODO:
This script currently ignores /dev/sda and /dev/nvme0n1 and all their partitions. This is non-configurable at the moment, the only way to change this behaviour is to edit the `functions/mnt.fish` file and change the `set _MNT_SEEN_DEVICES` line.
You can add ignroed devices using the `MNT_IGNORE_DEVICES` environment variable. We might be able to phase out the `_MNT_SEEN_DEVICES` variable in favour of this. On the other hand, it's questionable if we want to pollute the environment with some info that might be better suited to a config file. But if we have *two* config files, we should probably move them into a `.config/mnt/` folder or something. That's why I haven't done this refactor yet.
## License
These scripts are licensed under the MIT license

@ -0,0 +1,8 @@
function coalesce
for x in $argv
if test -n "$x"
echo "$x"
return
end
end
end

@ -0,0 +1,239 @@
set _MNT_SEEN_DEVICES /dev/sda /dev/nvme0n1
function mnt
argparse --name=mnt 'h/help' 'l/list' 'f/full-paths' 'u/unmount' -- $argv
if set -q _flag_help
echo "mnt - A mounting utility"
echo
echo "Usage: mnt -hlfu [IDENTIFIER]"
echo
echo "Flags"
echo " -l/--list List information on all available mounts (default behaviour if no IDENTIFIER"
echo " was specified)"
echo
echo " -h/--help Print this help page"
echo
echo " -u/--unmount Unmount specified path"
echo
echo " -f/--full-paths Print full paths instead of the shorter versions"
echo
echo "IDENTIFIER"
echo " The IDENTIFIER can be anything that identifies a line in the output of"
echo " mnt_core_list_mounts when using grep"
return 0
end
if set -q _flag_list; or ! count $argv > /dev/null
mnt_core_pretty_list_mounts $_flag_full_paths | sort
return 0
end
set -l selected_mount (mnt_core_list_mounts | rg -- (string trim -rc '/' -- $argv[1]))
if test (count $selected_mount) -gt 1
echo '"'"$argv[1]"'" is ambigous, it matched:'
for line in $selected_mount
echo " - "(string split \t $line)[1]
end
echo "Please be a little bit more precise!"
# TODO: allow the user to select one of the options
return 1
end
if test -z "$selected_mount"
echo "Mount point not found!"
return 1
end
set -l info (string split \t $selected_mount)
set -l device $info[1]
set -l path $info[2]
set -l mount_point (mnt_core_mount_point $info[1])
if set -q _flag_unmount
if test -z "$mount_point"
set_color red
echo $argv[1] "might not actually be mounted!"
set_color normal
end
echo $info[4]
eval $info[4]
else
if test -n "$mount_point"
echo $argv[1] "is already mounted at $mount_point!"
return 1
end
echo $info[3]
eval $info[3]
end
end
function mnt_core_pretty_list_mounts
argparse 'f/full-paths' -- $argv
for line in (mnt_core_list_mounts)
set -l info (string split \t $line)
set -l pretty_print_mode mount
if test -d $info[2]
set_color green
echo -n "[m] "
set pretty_print_mode unmount
else if test -b $info[1]
set_color yellow
echo -n "[u] "
else
set_color red
echo -n "[-] "
end
set pretty (string split '\t' (mnt_core_pretty_print_line $_flag_full_paths $pretty_print_mode $line))
if ! set -q _flag_full_paths
set pretty[1] (shorten_path $pretty[1])
end
echo $pretty
end
end
function mnt_core_pretty_print_line
argparse 'f/full-paths' -- $argv
set -l info (string split \t $argv[2])
set -l path_printer shorten_path
if set -q _flag_full_paths
set path_printer echo
end
# unpack info
set -l device $info[1]
set -l mount_path $info[2]
set -l mount_cmd $info[3]
set -l unmount_cmd $info[4]
set -l name (coalesce "$info[5]" ($path_printer (string replace '-' '' $mount_path)) "???")
set -l size $info[6]
set -l cmd "$unmount_cmd"
# decide if to print the mount or unmount cmd
if test $argv[1] = 'mount'
set cmd "$mount_cmd"
end
set -l tab \t
# print #name (size), but leave out missing parts
if test -n "$size"
if test -n "$name"
echo "$device"\t"$name ($size)"
else
echo "$device"\t"($size)"
end
else
if test -n "$name"
echo "$device"\t"$name"
else
echo "$device"\t"$cmd"
end
end
end
function mnt_core_list_mounts
# list things defined in ~/.mounts
set -l seen $_MNT_SEEN_DEVICES $MNT_IGNORE_DEVICES
for line in (cat ~/.mounts)
# filter out empty lines
if test -z "$line"
continue
end
# filter out comments
if string match -erq '^\s*#.+' "$line" 2> /dev/null
continue
end
set -l info (string split \t $line)
set -a seen $info[1]
set -l size (mnt_core_get_blockdevice_size $info[1])
echo $line\t"$size"
end
mnt_core_list_block_dev $seen
end
function mnt_core_list_block_dev
set -l seen $argv
# iterate normal block devices (sdXN)
for device in /dev/sd?
if string match -q -- $device $seen
continue
end
for part in $device?
if string match -q -- $device $seen
continue
end
# get the mount point, or - if it doesn't exist
set -l mount_point (coalesce (mnt_core_mount_point $part) -)
# get label and size
set -l label (mnt_core_get_blockdevice_label $part)
set -l size (mnt_core_get_blockdevice_size $part)
echo "$part"\t"$mount_point"\tudisksctl mount -b "'$part'"\tudisksctl unmount -b $part\t"$label"\t"$size"
end
end
end
function mnt_core_filter
while read line
set -l info (string split \t $line)
switch $argv[1]
case available avail
if test -b $info[1]
echo $line
end
case mounted
if mnt_core_mount_point $info[1] > /dev/null
echo $line
end
case unmounted
if test -b $info[1]; and ! mnt_core_mount_point $info[1] > /dev/null
echo $line
end
case '*'
echo $line
end
end
end
function mnt_core_mount_point
# get block device mount point or children mount point (if child is of type crypt)
# it was introduced to better handle encrypted setups, where not the block device, but the crypt container is mounted
set -l res (lsblk -J $argv 2>/dev/null | jq -r '.blockdevices[0].mountpoints[0] // ( if .blockdevices[0].children then (.blockdevices[0].children[] | select(.type == "crypt") | .mountpoints[0]) else "" end) // ""')
if test -z "$res"
return 1
end
echo $res
end
function mnt_core_get_blockdevice_size
set -l res (lsblk -J $argv 2>/dev/null | jq -r '.blockdevices[0].size // ""')
if test -z "$res"
return 1
end
echo $res
end
function mnt_core_get_blockdevice_label
blkid -o value --match-tag LABEL $argv
end

@ -0,0 +1,35 @@
function mount-luks
argparse 'u/unmount' 'h/help' -- $argv
if set -q _flag_help
echo "mount-luks - mount and unmount luks containers"
echo
echo "Usage for mounting: mount-luks BLOCK_DEVICE PASS_NAME MAPPER_NAME"
echo " where PASS_NAME is the name of the pass(1) key that contains the volume password"
echo " and MAPPER_NAME is the name of the luks mapper (required for unmounting)"
return 0
end
if set -q _flag_unmount
if test (count $argv) -lt 1 -o -z "$argv[1]"
echo "Usage: mount-luks -u MAPPTER_NAME"
end
set -l mapper $argv[1]
udisksctl unmount -b /dev/mapper/$mapper
sudo cryptsetup luksClose $mapper
else
if test (count $argv) -lt 3
echo "Usage: mount-luks BLOCK_DEVICE PASS_NAME MAPPER_NAME"
end
set -l device $argv[1]
set -l pass_name $argv[2]
set -l mapper $argv[3]
pass $PASS_NAME | head -n 1 | sudo cryptsetup luksOpen $device $mapper -
udisksctl mount -b /dev/mapper/$mapper
end
end

@ -0,0 +1,39 @@
# mount veracrypt volumes using keyfiles or passphrases
function mount-vc --argument device key_name target -d "Mount a veracrypt container with a keyfile stored in /home/anton/.keys"
if test -z "$argv[1]" -o \( "$argv[1]" = "--help" \) -o \( "$argv[1]" = "-h" \)
echo -e "mount-vc: Mount or dismount veracrypt volumes\n\
\n\
Usage:\n\
mount a volume using a keyfile:\n\
\n\
mount-vc <device> <key file name> <target>\n\
\n\
mount a volume using a password: (will be prompted for)\n\
\n\
mount-vc <device> <target>\n\
\n\
dismount a volume:\n\
\n\
mount-vc -u <target>\n"
return
end
if test "$argv[1]" = "-u"
sudo veracrypt -d "$argv[2]"
return
end
set dev "$argv[1]"
set target "$argv[3]"
set opts "-t" "--non-interactive"
if test (count $argv) = 2
read -s -P "Volume password: " pass
set target "$argv[2]"
set -a opts "--password=$pass"
else
set -a opts "-k" "$HOME/.keys/$argv[2]" "-p" ""
end
sudo veracrypt $opts "$dev" "$target"
end

@ -0,0 +1,17 @@
function shorten_path
if ! string match -q -- '*/*' $argv[1]
echo $argv[1]
return
end
set -l segments (string split '/' (string trim -r -c '/' $argv[1]))
if test -n $segments[1]
echo -n (string split '' $segments[1])[1]
end
for seg in $segments[2..-2]
echo -n '/'(string split '' $seg)[1]
end
echo '/'$segments[-1]
end

@ -0,0 +1,191 @@
#!/usr/bin/env fish
function print_warn
echo (set_color yellow)"WARN: $argv" (set_color normal)
end
function print_err
echo (set_color red)"ERROR: $argv" (set_color normal)
end
function to_stderr
while read -l line
echo $line 1>&2
end
end
function check_program_installed
# check if program $argv[1] is installed, if not, print a warning
if ! command -vq $argv[1]
print_err "External program $argv[1] is required, please install it using your package manager of choice!" | to_stderr
return 1
end
return 0
end
function install_symlink
# install $argv[1] to $argv[2] using a symlink
# return 0 if it worked or if the target exists but is a symlink to $argv[1]
# otherwise return 1
# (--force overwrites the behavour to overwrite install)
argparse 'f/force' -- $argv
if ! ln -s $flag_force $argv[1] $argv[2] 2> /dev/null
if test (realpath $argv[2]) = $argv[1]
return 0
end
print_warn "File $argv[2] already exists and is not a symlink to $argv[1]!" | to_stderr
return 1
end
end
function install_copy
# install $argv[1] to $argv[2] by copying it, doesn't overwrite if no -f/--force flag is given
# return 1 if the file couldn't be copied
argparse 'f/force' -- $argv
if test -f $argv[2]; and ! set -q _flag_force
print_warn "File $argv[2] already exists, use --force to overwrite existing files." | to_stderr
return 1
end
cp $argv[1] $argv[2]
end
function install
argparse 'h/help' 'c/copy' 'f/force' 'e/extras' 'C/clean' -- $argv
# get path to this script, which is the repo path
set DIR (cd (dirname (status -f)); and pwd)
if set -q _flag_help
echo "mnt install script:"
echo
echo "Usage: ./install.sh -h/--help -c/--copy -f/--force -e/--extras -C/--clean"
echo
echo "Flags:"
echo " -h/--help Show this messasge"
echo
echo " -c/--copy Copy files instead of symlinking"
echo
echo " -f/--force Overwrite existing files"
echo
echo " -e/--extras Copy helpful extras such as mount-luks and mount-vc"
echo
echo " -C/--clean Uninstall all installed files. Uninstalls extras when used with -e"
echo
return 0
end
set -l external_dependencies rg blkid jq udisksctl
set -l core_files functions/mnt.fish completions/mnt.fish
set -l dependencies functions/{coalesce,shorten_path}.fish
set -l extras functions/{mount-luks,mount-vc}.fish
set -l fish_config_path $HOME/.config/fish
# get fish config path from argv[1]
if test (count $argv) -gt 0
set fish_config_path $argv[1]
end
# figure out the command we use for installing files
set -l install_cmd install_symlink $_flag_force
if set -q _flag_copy
set install_cmd install_copy $_flag_force
end
# find path to fish config
if ! test -f $fish_config_path/config.fish
print_err "Could not find fish config at $fish_config_path/config.fish" | to_stderr
return 1
end
# check for uninstall flag
if set -q _flag_clean
# uninstall mode!
echo "Uninstalling mnt..."
for file in $core_files $dependencies
if test -f $fish_config_path/$file
rm $fish_config_path/$file
echo " Removed $fish_config_path/$file"
else
echo " $file was not installed."
end
end
if set -q _flag_extras
echo "Uninstalling extras..."
for file in $extras
if test -f $fish_config_path/$file
rm $fish_config_path/$file
echo " Removed $fish_config_path/$file"
else
echo " $file was not installed."
end
end
end
return 0
end
# check external dependencies
for dep in $external_dependencies
check_program_installed $dep; or return 1
end
# install core files
for file in $core_files
if ! $install_cmd $DIR/$file $fish_config_path/$file
print_err "Aborting installation..."
return 1
end
set_color green
echo "Installed $file..."
set_color normal
end
# install deps
for file in $dependencies
if $install_cmd $DIR/$file $fish_config_path/$file
set_color green
echo "Installed $file..."
set_color normal
else
print_warn "Could not install dependency "(basename $file)", installer will continue, but installation might be incomplete." | to_stderr
end
end
# install extras if requested
if set -q _flag_extras
for file in $extras
if $install_cmd $DIR/$file $fish_config_path/$file
set_color green
echo "Installed $file..."
set_color normal
else
print_warn "Could not install extra "(basename $file)", installer will continue, but installation might be incomplete." | to_stderr
end
end
end
# install .mount file
if test -f $HOME/.mounts
set_color green
echo ".mounts file already installed!"
set_color normal
else
cp $DIR/.mounts $HOME/.mounts
echo "Provided a clean .mounts file in $HOME/.mounts"
end
echo
set_color green
echo ">>> Installation complete! <<<"
set_color normal
end
if test "$_" != source
install $argv
exit $status
end
Loading…
Cancel
Save