Instead of cat "$x" | command or echo "$x" | command, use command <$x (vs cat) or command <<<$x (vs echo): it saves a fork and removes the need to quote.
Instead of if [ x -lt y ] use if [[ x -lt y ]]: it saves a fork ([[ is a bash builtin; help test for details) and adds some functionality.
Functions return their last exit value already so contains() can be shortened to contains() { test "${1#*$2}" != "$1"; } Whether you prefer this is up to you.
Use bash defaulting mechanism instead of if [[ -z , as in CONF=${2:-./steps.json}
Use for ((i=0; i<$LIMIT; i++)) instead of i=0; while ...
Test the exit values of things that shouldn't fail, as in mkdir -p "$DESTROOT" || exit 1. Any invocation of cd or pushd should be checked for success, always! A general purpose DIE() function can replace the naked exit and take an error message as an argument. If nothing should fail, set -e or trap DIE ERR (the first argument is a function name) does this globally.
Constructions like jq -r ".["$i"].files | length") and echo " ""$FSRC" are kind of weird and the inner double quotes probably should be removed.
In a language where every variable is a global, it's a good habit to use fewer variables. For example, RES=$(foo); LOOP=$( echo "$RES" | ...) can just be LOOP=$( foo | ...)
Your get-conf pattern should be in a function like get_conf() { jq -r $1<<<$CONF; }
Pruning code paths is important in an interpreted language. Since the wildcard copy method works for regular copies too, just use that one unconditionally and remove if contains ... "\*"
You don't need to escape wildcards like * in double quotes. When in doubt about what will be interpolated, use single quotes. Quoting in bash can be very complex and take a long time to learn; an advanced understanding of it will help to avoid common bugs.
Since you are using commands that aren't standard, it's a good idea to set PATH in the script, or as an optional config directive, and to check that they're there before you begin, as in require() { for cmd in $@;"$@"; do type $cmd >/dev/null || exit 1; done; } followed by require jq udisksctl
Read CONF just once, into a variable: conf=$(<$CONF), and query that. Then you can edit the config while the script runs.