ossuary script to mount, unmount geli encrypted containers

ossuary

Ive created a script called ossuary which will mount and unmount geli encryped containers with geli keys

you create a file which you use as a geli encrypted container with a key file and format the container as zfs,
and set the zfs mount point which the script uses to mount the device which is described below

Script usage

* mount device

Bash:
sudo ossuary mount -f ~/documents/disk.img -k ~/.ossuary/ossuary.key

* umount device

Bash:
sudo ossuary umount -f ~/documents/disk.img

-f and the path to the geli container

-k and the path to the geli key file

ossuary script - sh version

Bash:
#!/bin/sh

# script usage
script_usage=$(printf "%s\n" \
"mount: sudo $(basename "$0") mount -f container -k gelikey" \
"umount: sudo $(basename "$0") umount -f container")

# check to see if script was run as root
if [ "$(id -u)" != "0" ]; then
   printf "%s\n" "$0 must be run as root using sudo" "$script_usage"
   exit 1
fi

# mount function
mount () {
    # group commands
    {
    # container
    container="$1" && \
    # gelikey
    gelikey="$2" && \

    # mdconfig loopname from container
    loopdevice=$(mdconfig -lf "$container" | sed 's/[ \t]*$//')

    # eli filepath
    loopcrypt="/dev/${loopdevice}.eli"

    # mdconfig create vnode from container
    printf "%s\n" "+ mdconfig creating vnode for '$container'" && \
    loop=$(mdconfig -a -t vnode -f "$container") && \

    # geli attach key to vnode
    printf "%s\n" "+ geli attaching '$gelikey' key to '$container' file" && \
    geli attach -k "$gelikey" "$loop" && \

    # mdconfig loop device for container
    loopdevice=$(mdconfig -lf "$container" | sed 's/[ \t]*$//') && \

    # path to mdconfig eli file
    loopcrypt="/dev/${loopdevice}.eli" && \

    # zpool name from mdconfig eli file
    poolname=$(zdb -l "$loopcrypt" | awk -F\' '/[[:blank:]]name/ {print $2; exit;}') && \

    # zpool import pool
    printf "%s\n" "+ zpool importing '$poolname'" && \
    zpool import "$poolname" && \

    # mount point from zpool
    mountpoint=$(zfs get -H -o value mountpoint "$poolname") && \
    printf "%s\n" "+ '$poolname' mounted to '$mountpoint'";
    } || { mdconfig -du "$loopdevice" && exit; }
}

# umount function
umount () {
    # group commands
    {
    # container
    container="$1" && \

    # mdconfig loopname from container
    loopdevice=$(mdconfig -lf "$container" | sed 's/[ \t]*$//') && \

    # eli filepath
    loopcrypt="/dev/${loopdevice}.eli" && \

    # zpool name from eli file
    poolname=$(zdb -l "$loopcrypt" | awk -F\' '/[[:blank:]]name/ {print $2; exit;}') && \

    # zfs umount poolname
    printf "%s\n" "- zfs unmounting '$poolname'" && \
    zfs umount "$poolname" && \

    # zpool export poolname
    printf "%s\n" "- zpool exporting '$poolname'" && \
    zpool export "$poolname" && \
    sleep 1 && \

    # geli detach
    printf "%s\n" "- geli detaching '$loopcrypt'" && \
    geli detach "$loopcrypt" && \

    # mdconfig remove md file
    printf "%s\n" "- mdconfig clearing '$loopdevice'" && \
    mdconfig -du "$loopdevice" && \
    printf "%s\n" "- unmounted device";
    } || { printf "%s\n" 'container not mounted' && exit; }
}

# check if mount is first argument
# + 2nd argument shuld be -f for the file to mount
# + 3rd argument should be the path to the file to mount
# + 4th argunent should be -k for key
# + 5th argument should be the path to the keyfile

# check if umount is first argument
# + 2nd argument shuld be -f for the file to unmount
# + 3rd argument should be the path to the file to unmount

# check arguments
if [ "$1" = mount ] && [ $# -eq 5 ]; then
   # group commands
   {
   [ "$2" = '-f' ] && \
   [ -f "$3" ] && \
   [ "$4" = '-k' ] && \
   [ -f "$5" ];
   } || { printf "%s\n" "$script_usage" && exit; }
   # mount function pass conatainer and key to function
   mount "$3" "$5"
elif [ "$1" = umount ] && [ $# -eq 3 ]; then
   # group commands
   {
   [ "$2" = '-f' ] && \
   [ -f "$3" ];
   } || { printf "%s\n" "$script_usage" && exit; }
   # umount function pass container to function
   umount "$3"
else
   printf "%s\n" "$script_usage"
fi

ossuary - sh version github

ossuary script - bash version

Bash:
#!/usr/local/bin/bash

# geli container mount and umount script
#=======================================

# script usage
script_usage=$(printf "%s\n%s\n" \
"mount: sudo $(basename "$0") mount -f container -k gelikey" \
"umount: sudo $(basename "$0") umount -f container")

# check to see if script was run as root
if [[ $UID -ne 0 ]]; then
  printf "%s\n" "$0 must be run as root using sudo" &&
  printf "%s\n" "$script_usage"
  exit 1
fi

# mount function
mount () {
    # group commands
    {
    # container
    container="$1" && \
    # gelikey
    gelikey="$2" && \

    # mdconfig loopname from container
    loopdevice=$(mdconfig -lf "$container" | sed 's/[ \t]*$//')

    # eli filepath
    loopcrypt="/dev/${loopdevice}.eli"

    # mdconfig create vnode from container
    printf "%s\n" "+ mdconfig creating vnode for '$container'" && \
    loop=$(mdconfig -a -t vnode -f "$container") && \

    # geli attach key to vnode
    printf "%s\n" "+ geli attaching '$gelikey' key to '$container' file" && \
    geli attach -k "$gelikey" "$loop" && \

    # mdconfig loop device for container
    loopdevice=$(mdconfig -lf "$container" | sed 's/[ \t]*$//') && \

    # path to mdconfig eli file
    loopcrypt="/dev/${loopdevice}.eli" && \

    # zpool name from mdconfig eli file
    poolname=$(zdb -l "$loopcrypt" | awk -F\' '/[[:blank:]]name/ {print $2; exit;}') && \

    # zpool import pool
    printf "%s\n" "+ zpool importing '$poolname'" && \
    zpool import "$poolname" && \

    # mount point from zpool
    mountpoint=$(zfs get -H -o value mountpoint "$poolname") && \
    printf "%s\n" "+ '$poolname' mounted to '$mountpoint'";
    } || { mdconfig -du "$loopdevice" && exit; }
}

# umount function
umount () {
    # group commands
    {
    # container
    container="$1" && \

    # mdconfig loopname from container
    loopdevice=$(mdconfig -lf "$container" | sed 's/[ \t]*$//') && \

    # eli filepath
    loopcrypt="/dev/${loopdevice}.eli" && \

    # zpool name from eli file
    poolname=$(zdb -l "$loopcrypt" | awk -F\' '/[[:blank:]]name/ {print $2; exit;}') && \

    # zfs umount poolname
    printf "%s\n" "- zfs unmounting '$poolname'" && \
    zfs umount "$poolname" && \

    # zpool export poolname
    printf "%s\n" "- zpool exporting '$poolname'" && \
    zpool export "$poolname" && \
    sleep 1 && \

    # geli detach
    printf "%s\n" "- geli detaching '$loopcrypt'" && \
    geli detach "$loopcrypt" && \

    # mdconfig remove md file
    printf "%s\n" "- mdconfig clearing '$loopdevice'" && \
    mdconfig -du "$loopdevice" && \
    printf "%s\n" "- unmounted device";
    } || { printf "%s\n" 'container not mounted' && exit; }
}

# check if mount is first argument
# + 2nd argument shuld be -f for the file to mount
# + 3rd argument should be the path to the file to mount
# + 4th argunent should be -k for key
# + 5th argument should be the path to the keyfile

# check if umount is first argument
# + 2nd argument shuld be -f for the file to unmount
# + 3rd argument should be the path to the file to unmount

# check arguments
if [[ "$1" =~ ^mount$ && $# -eq 5 ]]; then
   # group commands
   {
   [[ "$2" =~ '-f' ]] && \
   [[ -f "$3" ]] && \
   [[ "$4" =~ '-k' ]] && \
   [[ -f "$5" ]];
   } || { printf "%s\n" "$script_usage" && exit; }
   # mount function pass conatainer and key to function
   mount "$3" "$5"
elif [[ "$1" =~ ^umount$ && $# -eq 3 ]]; then
   # group commands
   {
   [[ "$2" =~ '-f' ]] && \
   [[ -f "$3" ]];
   } || { printf "%s\n" "$script_usage" && exit; }
   # umount function pass container to function
   umount "$3"
else
   printf "%s\n" "$script_usage"
fi

ossuary bash version on githib

Install ossuary script

* create a bin directory in your home directory

Bash:
mkdir -p ~/bin

* change permissions

Bash:
chmod 700 ~/bin

* copy the ossuary script to your ~/bin directory

make the ossuary script executable

Bash:
chmod u+x ~/bin/ossuary

* edit your bashrc and add your bin directory to your bash path

Bash:
vim ~/.bashrc

add the code below to your bashrc

Bash:
# home bin
if [ -d "$HOME/bin" ]; then
   PATH="$HOME/bin:$PATH"
fi

* source your bashrc file

Bash:
source ~/.bashrc

What the script outputs in the terminal when you mount a geli encrypted container

Bash:
sudo ossuary mount -f ~/documents/disk.img -k ~/.ossuary/ossuary.key
Password:
+ mdconfig creating vnode for '/home/username/documents/disk.img'
+ geli attaching '/home/username/.ossuary/ossuary.key' key to '/home/username/documents/disk.img' file
Enter passphrase:
+ zpool importing 'crypt'
+ 'crypt' mounted to '/usr/home/username/mnt'

* first you are prompted for your sudo passsword

the script then shows that it is creating a vnode for your geli container, it will display your username and not the actual word username
then the script shows the path for the geli key and the path to the geli container

you are then prompted for the geli key password

the script then shows it is importing the zfs pool name
and the pool is then mounted to the location of the zfs mount point you set on the geli container

What the script outputs in the terminal when you umount a geli encrypted container

Bash:
sudo ossuary umount -f ~/documents/disk.img
Password:
- zfs unmounting 'crypt'
- zpool exporting 'crypt'
- geli detaching '/dev/md0.eli'
- mdconfig clearing 'md0'
- unmounted device

The script prompts you for your sudo password and then provides feedback on umounting the zfs pool,
exporting the zfs pool, detatching the geli file and using mdconfig to clear the md0 file

If you enter the wrong geli password the script will exit and automatically use mdconfig to clear the md0 file

Next you need to create the geli encrypted container as outlined below before you can use the script

Set up geli encrypted conatiner

Create geli encrypted container

Load geli Support
Support for geli is available as a loadable kernel module. To configure the system to automatically load the module at boot time, add the following line to /boot/loader.conf:

Bash:
geom_eli_load="YES"

create virtual container with dd

create a 2 gig container with dd on the Desktop called disk.img

* change directory to the Desktop

Bash:
cd ~/Desktop

switch to root

Bash:
sudo su

* use dd to create a 2 gig disk image

Bash:
dd if=/dev/zero of=disk.img bs=1M count=2048

* mount the image with mdconfig

Bash:
mdconfig -a -t vnode -f disk.img -u 0

Here, the -a option forces the disk mounting, -t vnode is used for opening a regular file, and the path of this file is specified after -f. The -u 0 option set the virtual disk identifier to use, in this case /dev/md0.

* Generate the Master Key

create directory to store geli key

Bash:
mkdir ~/.ossuary

Now we want to create a key for GELI to encrypt with, and attach it to our disk image device:
replace username with your username

Bash:
cd ~/Desktop

Add the location of your geli key

Bash:
dd if=/dev/zero of=/usr/home/username/.ossuary/ossuary.key bs=256 count=1
geli init -e aes -l 256 -s 4096 -K /usr/home/username/.ossuary/ossuary.key /dev/md0
geli attach -k /usr/home/username/.ossuary/ossuary.key /dev/md0

Enter passphrase:

Create the ZFS File System

* use dd to write random data to geli container before adding file system

Bash:
dd if=/dev/random of=/dev/md0.eli bs=1M

* To create a simple, non-redundant pool using a single disk device:

Bash:
zpool create crypt /dev/md0.eli

* add compression and duplication to the zfs pool

Bash:
zfs set compression=lz4 crypt

* zfs set mount point

create the mount point for zfs

Bash:
mkdir -p ~/mnt

change the permissions on the mount point

Bash:
chmod 700 ~/mnt

set the zfs mount point
replace username with your username

Bash:
zfs set mountpoint=/usr/home/username/mnt crypt

* change the permission on the container
replace username with your username

Bash:
chmod -R username:username ~/mnt

Finally, when you want to unmount, we also want to detach from GELI and detach from md:

zfs unmount

Bash:
zfs umount crypt

* geli detach

Bash:
geli detach md0.eli

* mdconfig free loop device

Bash:
mdconfig -d -u 0

manual mounting and unmounting

* mount

use mdconfig to mount the encrypted container to /dev/md0

Bash:
mdconfig -a -t vnode -f disk.img -u 0

* use geli with the path to the key and device
replace username with your username

Bash:
geli attach -k /usr/home/username/.ossuary/ossuary.key /dev/md0

* we need to import the zpool which will also mount the container

Bash:
zpool import crypt

* umount

unmount the zfs pool

Bash:
zfs umount crypt

* we need to export the zfs pool before we use geli detach, otherwise geli thinks the device is busy

Bash:
zpool export crypt

* use geli to detach the encrypted device

Bash:
geli detach md0.eli

* free the loop device

Bash:
mdconfig -d -u 0
 
Last edited:
No point in using && in line continuations unless you want to express a construct where the second line is executed only if the first one is successful (grouped together with a test(1)). I didn't spot any such in this script however.

And for the love of (insert your favorite deity here), please stop writing bash scripts where a plain /bin/sh script is completely sufficient. As far as I could see every line you wrote is compatible with FreeBSD's /bin/sh or if they aren't they can be modified with minimal ease.
 
You're completely misunderstanding printf:
Code:
printf "%s\n" "$0 must be run as root using sudo"

No need for printf at all here,
Code:
echo "$0 must be run as root using sudo"
Or, if you insist on printf:
Code:
printf "%s must be run as root using sudo\n" $0
 
What's the point of the && \ at the end of each line?
HI Mate
the &&\ are required for the script to run properly

i use mdconfig to create the vnode for the geli container and then use geli to attach the key in a grouped command
if you enter the wrong geli password in the grouped command i then use an or statement and use mdconfig to remove the md file
connected to the geli container
 
No point in using && in line continuations unless you want to express a construct where the second line is executed only if the first one is successful (grouped together with a test(1)). I didn't spot any such in this script however.

And for the love of (insert your favorite deity here), please stop writing bash scripts where a plain /bin/sh script is completely sufficient. As far as I could see every line you wrote is compatible with FreeBSD's /bin/sh or if they aren't they can be modified with minimal ease.
HI Mate

im using some bashism's to check the arguments passed to the script
for example im using the =~ operator to check if mount or umount is the first argument

Bash:
[[ "$1" =~ ^mount$ && $# -eq 5 ]]

Does sh have an equivalent command or would i have to use something like sed to run that check
 
Well, test(1) is an external program and a built-in in both sh(1) and shells/bash:

Code:
s1 = s2       True if the strings s1 and s2 are identical.

So you could write simply:

Code:
if [ "$1" =  "mount" && $# -eq 5 ]; then
    ...
fi

There's at least one gotcha with the = operator, if the arguments have a variable expansion you have to double quote them to guard against white space splitting and empty values:

Code:
if [ "$a" = "$b" ]; then
    ...
fi
 
You're completely misunderstanding printf:
Code:
printf "%s\n" "$0 must be run as root using sudo"

No need for printf at all here,
Code:
echo "$0 must be run as root using sudo"
Or, if you insist on printf:
Code:
printf "%s must be run as root using sudo\n" $0
Hi Mate

ive always used this syntax

Code:
printf "%s\n" "$0 must be run as root using sudo"
Well, test(1) is an external program and a built-in in both sh(1) and shells/bash:

Code:
s1 = s2       True if the strings s1 and s2 are identical.

So you could write simply:

Code:
if [ "$1" =  "mount" && $# -eq 5 ]; then
    ...
fi

There's at least one gotcha with the = operator, if the arguments have a variable expansion you have to double quote them to guard against white space splitting and empty values:

Code:
if [ "$a" = "$b" ]; then
    ...
fi
Hi kpa

thanks for the peer review

i have created a sh posix version of ossuary as well as a bash version

ossuary - sh version github

ossuary - bash version github

i updated the main post as well
i have tested the new sh version and it works fine
 
ive always used this syntax
Technically it's not wrong, just an odd way of doing things because there are two substitutions happening when one would do.

The first substitution happens when $0 is translated and inserted into the string, then you tell printf to print a string which is the previously processed string, causing the second substitution. The whole idea of a format string is to separate the text from the variables and use placeholders for the variables, not to replace the whole thing with a processed string that includes variables of its own.

So this makes more sense:
Code:
printf "%s must be run as root" $0
Or
Code:
echo "$0 must be run as root"

The same idea with && \, it's technically not an error but logically doesn't make sense.
 
I'm pretty sure you have to double quote $0 there to guard against the odd case that someone uses spaces in their filenames.

Code:
printf "%s must be run as root" "$0"

Imagine if the path to your script is /path/to/my script.sh and it's ran like "/path/to/my script"

White space splitting is going to make $0 into two strings "/path/to/my" and "script.sh" if it's not double quoted and I think that will give you two separate lines like:

Code:
/path/to/my must be run as root
script.sh must be run as root
 
I'm pretty sure you have to double quote $0 there to guard against the odd case that someone uses spaces in their filenames.
You are absolutely right. In this case I'd probably never use printf to begin with, a simple echo would be more than sufficient. The only reason I'd use printf (in a shell script) would to control the format of the output (to line up tables for example).
 
Back
Top