nixpkgs/pkgs/tools/filesystems/bees/bees-service-wrapper
Charles Duffy cb657d4ad1 bees: Try all findmnt modes in service wrapper
As reported by @Evils-Devils in Zygo/bees#123, the bees service wrapper
can fail on account of `findmnt` not being able to identify a mounted
filesystem using the default (kernel-introspection) mechanism.

Fall back to mtab and fstab-based inspection in turn should this fail.
2020-05-09 09:39:04 +02:00

228 lines
8.5 KiB
Bash
Executable File

#!@bash@/bin/bash
PATH=@bash@/bin:@coreutils@/bin:@utillinux@/bin:@btrfsProgs@/bin:$PATH
beesd_bin=@bees@/lib/bees/bees
# PLEASE KEEP NIX-ISMS ABOVE THIS LINE TO EASE UPSTREAM MERGE
#!/usr/bin/env bash
shopt -s extglob
# Upstream wrapper requires UUID to be used for configuration.
# However, when declaratively describing a host, we may not know its UUID, and
# shouldn't need to persist something that will differ between hosts built from
# the same configuration template.
# Thus, for using bees from NixOS, we have our own wrapper, which supports not
# just UUID but any specification permitted by findmnt
[[ $bees_debug ]] && { PS4=':${BASH_SOURCE##*/}:$LINENO+'; set -x; }
usage() {
cat >&2 <<EOF
Usage: ${BASH_SOURCE##*/} run|cleanup config-name|fsSpec [idxSizeMB=...] [verbosity=...] [workDir=...] [-- daemon-options...]
fsSpec should be in a format recognized by findmnt. Alternately,
"config-name" may refer to a file that exists in ${bees_config_dir:-/etc/bees}
with a .conf extension; if that file does not specify UUID, findmnt will be
used in addition.
Note that while config files may presently use shell arithmetic, use of this
functionality is not encouraged going forward: Setting ''idxSizeMB=4096'' is
preferred over ''DB_SIZE=$((1024*1024*1024*4))'' or ''DB_SIZE=$(( AL16M * 256 ))'',
although both of these are presently supported.
If fsSpec contains a /, it assumed to be a mount point to be looked up by
findmnt, not a config file name.
daemon-options are passed directly through to the daemon on startup, as
documented at https://github.com/Zygo/bees/blob/master/docs/options.md.
EOF
exit 1
}
die() { echo "$*" >&2; exit 1; }
allConfigNames=( blockdev fsSpec home idxSize idxSizeMB mntDir runDir status verbosity workDir )
# Alternate names for configuration values; "bees_" will always be prepended
declare -A altConfigNames=(
# from original bees wrapper
[BEESHOME]=home
[BEESSTATUS]=status
[MNT_DIR]=mntDir
[UUID]=uuid
[WORK_DIR]=runDir
[DB_SIZE]=idxSize
)
# legacy bees config files can be arbitrary shell scripts, so we need to actually evaluate them
sandboxedConfigFileEval() {
bash_exe=$(type -P bash) || exit
PATH=/var/empty ENV='' BASH_ENV='' AL128K="$((128*1024))" AL16M="$((16*1024*1024))" "$bash_exe" -r ${bees_debug+-x} \
-c 'eval "$(</dev/stdin)" >&2; for var; do [[ ${!var} ]] && printf "%q=%s\\0" "$var" "${!var}"; done' \
"${!altConfigNames[@]}" "${allConfigNames[@]}" \
<"$1"
}
readConfigFileIfExists() {
local line
[[ -s $1 ]] || return 1
while IFS= read -r -d '' line; do
line=${line%%+([[:space:]])"#"*}
[[ $line ]] || continue
[[ $line = *=* ]] || {
printf 'WARNING: Config file line not recognized: %q\n' "$line" >&2
continue
}
set_option "$line"
done < <(sandboxedConfigFileEval "$1")
}
set_option() {
local k v
k="${1%%=*}" v="${1#*=}"
[[ ${altConfigNames[$k]} ]] && k=${altConfigNames[$k]}
printf -v "bees_$k" %s "$v"
}
uuid_re='^[[:xdigit:]]{8}-[[:xdigit:]]{4}-[[:xdigit:]]{4}-[[:xdigit:]]{4}-[[:xdigit:]]{12}$'
# Shared code for setting configuration used by other operations.
#
# Reads from global associative array "opts" containing options passed in as
# key=value pairs on the command line, looks for config-file overrides, and
# sets individual global variables.
_setup() {
declare fstype
bees_fsSpec=$1; shift
# Look for file-based configuration, additional to honoring configuration on the command line
bees_config_dir="${bees_config_dir:-/etc/bees}"
if [[ $bees_fsSpec =~ $uuid_re ]]; then
bees_uuid=$bees_fsSpec
# If our spec looks like a bare UUID, and no config file exists in the new
# format, fall back to legacy config file search mechanism (grep; ewww).
if ! readConfigFileIfExists "$bees_config_dir/UUID=$bees_fsSpec.conf"; then
# Legacy approach to finding a config file: Grep for a *.conf file
# containing the UUID within its text. Permitting spaces around the "="
# appears to be a bug, but is retained for compatibility with the
# original upstream script.
allConfFiles=( "$bees_config_dir"/*.conf )
if (( ${#allConfFiles[@]} )); then
# in read or readarray with -d '', the NUL terminating the empty string is used as delimiter character.
readarray -d '' -t matchingConfFiles < <(grep -E -l -Z "^[^#]*UUID[[:space:]]*=[[:space:]]*" "${allConfFiles[@]}")
else
matchingConfFiles=( )
fi
if (( ${#matchingConfFiles[@]} == 1 )); then
# Exactly one configuration file exists in our target directory with a reference to the UUID given.
bees_config_file=${matchingConfFiles[0]}
readConfigFileIfExists "$bees_config_file"
echo "NOTE: Please consider renaming $bees_config_file to $bees_config_dir/UUID=$bees_fsSpec" >&2
echo " ...and passing UUID=$bees_fsSpec on startup." >&2
elif (( ${#matchingConfFiles[@]} > 1 )); then
# The legacy wrapper would silently use the first file and ignore
# others, but... no.
echo "ERROR: Passed a bare UUID, but multiple configuration files match it:" >&2
printf ' - %q\n' "${matchingConfFiles[@]}" >&2
die "Unable to continue."
fi
fi
else
# For a non-UUID fsSpec that is not a path, look only for a config file
# exactly matching its text.
#
# (Passing a mount point as a fsSpec is only supported with the new
# wrapper; all key=value pairs can be passed on the command line in this
# mode, so config file support is not needed).
[[ $bees_fsSpec = */* ]] || readConfigFileIfExists "$bees_config_dir/$bees_fsSpec.conf"
fi
[[ $bees_uuid ]] || {
# if bees_uuid is not in our .conf file, look it up with findmnt
for findmnt_mode in --kernel --mtab --fstab; do
read -r bees_uuid fstype < <(findmnt "$findmnt_mode" -n -o uuid,fstype "$bees_fsSpec") ||:
[[ $bees_uuid && $fstype ]] && break
done
[[ $bees_uuid ]] || die "Unable to identify a device matching $bees_fsSpec with findmnt"
[[ $fstype = btrfs ]] || die "Device type is $fstype, not btrfs"
}
[[ $bees_uuid = */* ]] || readConfigFileIfExists "$bees_config_dir/UUID=$bees_uuid.conf"
# Honor any values read from config files above; otherwise, set defaults.
bees_workDir="${bees_workDir:-.beeshome}"
bees_runDir="${bees_runDir:-/run/bees}"
bees_mntDir="${bees_mntDir:-$bees_runDir/mnt/$bees_uuid}"
bees_home="${bees_home:-$bees_mntDir/$bees_workDir}"
bees_status="${bees_status:-${bees_runDir}/$bees_uuid.status}"
bees_verbosity="${bees_verbosity:-6}"
bees_idxSizeMB="${bees_idxSizeMB:-1024}"
bees_idxSize=${bees_idxSize:-"$(( bees_idxSizeMB * 1024 * 1024 ))"}
bees_blockdev=${bees_blockdev:-"/dev/disk/by-uuid/$bees_uuid"}
[[ -b $bees_blockdev ]] || die "Block device $bees_blockdev missing"
(( bees_idxSize % (16 * 1024 * 1024) == 0 )) || die "DB size must be divisible by 16MB"
}
do_run() {
local db old_db_size
_setup "$1"; shift
mkdir -p -- "$bees_mntDir" || exit
# subvol id 5 is reserved for the root subvolume of a btrfs filesystem.
mountpoint -q "$bees_mntDir" || mount -osubvolid=5 -- "$bees_blockdev" "$bees_mntDir" || exit
if [[ -d $bees_home ]]; then
btrfs subvolume show "$bees_home" >/dev/null 2>&1 || die "$bees_home exists but is not a subvolume"
else
btrfs subvolume create "$bees_home" || exit
sync # workaround for Zygo/bees#93
fi
db=$bees_home/beeshash.dat
touch -- "$db"
old_db_size=$(stat -c %s -- "$db")
new_db_size=$bees_idxSize
if (( old_db_size != new_db_size )); then
rm -f -- "$bees_home"/beescrawl."$bees_uuid".dat
truncate -s "$new_db_size" -- "$db" || exit
fi
chmod 700 -- "$bees_home"
# BEESSTATUS and BEESHOME are the only variables handled by the legacy
# wrapper for which getenv() is called in C code.
BEESSTATUS=$bees_status BEESHOME=$bees_home exec "${beesd_bin:-/lib/bees/bees}" \
--verbose "$bees_verbosity" \
"$@" "$bees_mntDir" || exit
}
do_cleanup() {
_setup "$1"; shift
mountpoint -q "$bees_mntDir" && umount -l -- "$bees_mntDir" || exit
}
(( $# >= 2 )) || usage
declare -f "do_$1" >/dev/null 2>&1 || usage
mode=$1; shift # must be a do_* function; currently "run" or "cleanup"
declare -a args=( "$1" ); shift # pass first argument (config-name|fsSpec) through literally
# parse other arguments as key=value pairs, or pass them through literally if they do not match that form.
# similarly, any option after "--" will be passed through literally.
while (( $# )); do
if [[ $1 = *=* ]]; then
set_option "$1"
elif [[ $1 = -- ]]; then
shift
args+=( "$@" )
break
else
args+=( "$1" )
fi
shift
done
"do_$mode" "${args[@]}"