Files
infix/src/confd/bin/migrate
T
2025-04-09 15:51:11 +02:00

254 lines
5.5 KiB
Bash
Executable File

#!/bin/sh
# Run .cfg migration jq scripts to backup and transform older syntax.
scripts="/usr/share/confd/migrate"
ident=$(basename "$0")
usage()
{
echo "usage: $0 [-chiq] [-b /path/backup/file.ext] [/path/to/config.cfg]"
echo
echo "options:"
echo " -b FILE Create backup FILE, appends detected version before .ext"
echo " -e Print all messages on stderr instead of syslog"
echo " -c Check only, returns false when migration is not needed"
echo " -h This help text"
echo " -i Edit file in-place instead of sending to stdout, like sed"
echo " -q Quiet, skip normal log messages, only errors are logged"
echo " -s PATH Specify custom script path"
echo
echo "By default, this script reads a .cfg file on stdin, or as the first"
echo "non-option argument, then migrates it to a new syntax on stdout."
}
# shellcheck disable=SC2317
cleanup()
{
rm -f "$tmp"
}
note()
{
if [ -n "$quiet" ]; then
return
fi
if [ -n "$nolog" ]; then
echo "$1" >&2
else
logger -I $$ -k -p user.notice -t "$ident" "$1"
fi
}
err()
{
if [ -n "$nolog" ]; then
echo "Error: $1" >&2
else
logger -I $$ -k -p user.err -t "$ident" "$1"
fi
}
# Convert human-readable version to integer level
atoi()
{
echo "$1" | awk -F. '{print $1 * 1000 + $2}'
}
file_version()
{
jq -r '
if .["infix-meta:meta"] | has("version") then
.["infix-meta:meta"]["version"]
else
"0.0"
end
' "$1"
}
# Find the latest version by examining the highest numbered directory
confd_version()
{
find "$scripts" -mindepth 1 -maxdepth 1 -type d \
| sort -V | tail -n1 | xargs -n1 basename
}
# Update meta data with the latest version
meta_version()
{
if jq --arg version "$sys_version" '.["infix-meta:meta"] = {"version": $version}' "$2" \
> "${2}.tmp" && mv "${2}.tmp" "$2"; then
note "$1: configuration updated to version $sys_version."
return 0
fi
err "$1: failed updating configuration to version $sys_version!"
return 1
}
# Apply the scripts for each version directory in sequence
migrate()
{
note "$1: migrating from version $cfg_version"
for version_dir in $(find "$scripts" -mindepth 1 -maxdepth 1 -type d | sort -V); do
dir=$(basename "$version_dir")
version=$(atoi "$dir")
# Step by step upgrade file to latest version
if [ "$cfg_level" -lt "$version" ]; then
note "Applying migrations for version $dir ..."
# Apply all scripts in the version directory in order
for script in $(find "$version_dir" -type f -name '*.sh' | sort -V); do
note "$1: calling $script ..."
sh "$script" "$2"
done
# File now at $version ...
cfg_level="$version"
fi
done
}
# Try migrating a copy, then diff the files, for factory-config check
diff()
{
CFG=$1
TMP=$(mktemp)
cp -p "$CFG" "$TMP"
quiet=1
migrate "(diff)" "$TMP"
cmp -s "$CFG" "$TMP"
rc=$?
rm -f "$TMP"
return $rc
}
tmp=$(mktemp)
chmod 600 "$tmp"
trap cleanup INT HUP TERM EXIT
OPTS=$(getopt -o b:echiqs: -- "$@")
eval set -- "$OPTS"
while [ -n "$1" ]; do
case $1 in
-b)
bak=$2
shift
;;
-e)
nolog=1
;;
-c)
check=1
;;
-h)
usage
exit 0
;;
-i)
inplace=1
;;
-q)
quiet=1
;;
-s)
scripts=$2
shift
;;
--)
shift
break
;;
*)
# Likely file argument
break
;;
esac
shift
done
if [ -n "$1" ] && [ -f "$1" ]; then
# Copy to tempfile to allow user to > same file
orig="$1"
cp -p "$1" "$tmp"
shift
elif [ -t 0 ]; then
orig="(stdin)"
cat > "$tmp"
else
usage
exit 1
fi
if ! jq empty "$tmp" 2>/dev/null; then
err "$tmp invalid JSON format!"
exit 1
fi
cfg_version=$(file_version "$tmp")
sys_version=$(confd_version)
cfg_level=$(atoi "$cfg_version")
sys_level=$(atoi "$sys_version")
# Check for downgrade
if [ "$cfg_level" -gt "$sys_level" ]; then
err "$orig: version is newer ($cfg_version) than supported ($sys_version). Exiting."
exit 1
fi
# If the current version is already the latest, exit the script
if [ "$cfg_level" -eq "$sys_level" ]; then
exit 0
else
if [ -n "$check" ]; then
# We may be called to check a file without meta:version (factory)
if [ "$cfg_version" = "0.0" ]; then
if diff "$tmp"; then
# File is OK, despite lacking meta:version
exit 0
fi
msg="$orig: has syntax error, requires migrating."
else
msg="$orig: version $cfg_version, requires migrating."
fi
if [ -t 0 ]; then
echo "$msg"
else
note "$msg"
fi
exit 1
fi
fi
if [ -n "$bak" ]; then
fil="${bak%.*}"
ext="${bak##*.}"
bak="${fil}-${cfg_version}.${ext}"
if cp -p "$tmp" "$bak" 2>/dev/null; then
note "$orig: backup created: $bak"
else
err "$orig: failed creating backup: $bak"
exit 1
fi
fi
if ! migrate "$orig" "$tmp"; then
exit 1
fi
meta_version "$orig" "$tmp"
if [ -n "$inplace" ] && [ "$orig" != "(stdin)" ]; then
cp -p "$tmp" "$orig"
else
cat "$tmp"
fi
exit 0