You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

411 lines
12 KiB

#!/usr/bin/env bash
# shellcheck disable=SC2002,2207
#------------------------------------------------------------------------------------
# Companion script for syno_hdd_db
# Schedule companion script to run as root at shut-down
#
# https://www.synology-forum.de/threads/neue-version-7-3-2-86009.140586/post-1265124
#------------------------------------------------------------------------------------
# TODO
# Check running on DSM version that needs it
# What to do if @database is on the NVMe volume?
scriptver="v1.0.0"
script=Synology_HDD_shutdown
#repo="007revad/Synology_HDD_db"
scriptname=syno_hdd_shutdown
ding(){
printf \\a
}
# Save options used for getopt
args=("$@")
if [[ $1 == "--trace" ]] || [[ $1 == "-t" ]]; then
trace="yes"
fi
# Check script is running as root
if [[ $( whoami ) != "root" ]]; then
ding
echo -e "ERROR This script must be run as sudo or root!"
exit 1 # Not running as sudo or root
fi
# Get NAS model
model=$(cat /proc/sys/kernel/syno_hw_version)
#modelname="$model"
# Show script version
#echo -e "$script $scriptver\ngithub.com/$repo\n"
echo "$script $scriptver"
# Get DSM full version
productversion=$(synogetkeyvalue /etc.defaults/VERSION productversion)
buildphase=$(synogetkeyvalue /etc.defaults/VERSION buildphase)
buildnumber=$(synogetkeyvalue /etc.defaults/VERSION buildnumber)
smallfixnumber=$(synogetkeyvalue /etc.defaults/VERSION smallfixnumber)
# Get CPU arch and platform_name
arch="$(uname -m)"
platform_name=$(synogetkeyvalue /etc.defaults/synoinfo.conf platform_name)
# Show DSM full version and model
if [[ $buildphase == GM ]]; then buildphase=""; fi
if [[ $smallfixnumber -gt "0" ]]; then smallfix="-$smallfixnumber"; fi
echo "$model DSM $productversion-$buildnumber$smallfix $buildphase"
# Show CPU arch and platform_name
echo "CPU $platform_name $arch"
# Show options used
if [[ ${#args[@]} -gt "0" ]]; then
echo -e "Using options: ${args[*]}\n"
else
echo ""
fi
# Check Synology has synonvme
if ! which synonvme >/dev/null; then
ding
echo "${model} does not have synonvme!"
exit 2 # NAS model does support NVMe
fi
# Check Synology has libsynonvme.so.1
if [[ ! -e /usr/lib/libsynonvme.so ]]; then
ding
echo "${model} does not have libsynonvme.so.1!"
exit 2 # NAS model does support NVMe
fi
#set -x
# Get script location
# https://stackoverflow.com/questions/59895/
source=${BASH_SOURCE[0]}
while [ -L "$source" ]; do # Resolve $source until the file is no longer a symlink
scriptpath=$( cd -P "$( dirname "$source" )" >/dev/null 2>&1 && pwd )
source=$(readlink "$source")
# If $source was a relative symlink, we need to resolve it
# relative to the path where the symlink file was located
[[ $source != /* ]] && source=$scriptpath/$source
done
scriptpath=$( cd -P "$( dirname "$source" )" >/dev/null 2>&1 && pwd )
scriptfile=$( basename -- "$source" )
echo "Running from: ${scriptpath}/$scriptfile"
# Warn if script located on M.2 drive
scriptvol=$(echo "$scriptpath" | cut -d"/" -f2)
vg=$(lvdisplay | grep /volume_"${scriptvol#volume}" | cut -d"/" -f3)
md=$(pvdisplay | grep -B 1 -E '[ ]'"$vg" | grep /dev/ | cut -d"/" -f3)
if cat /proc/mdstat | grep "$md" | grep nvme >/dev/null; then
ding
echo -e "WARNING Don't store this script on an NVMe volume!"
exit 3 # Script is stored on NVMe volume
fi
shutdown_log="${scriptpath}/${scriptname}.log"
# Delete old shutdown_log
if [[ -f "$shutdown_log" ]]; then
rm "$shutdown_log"
fi
progbar(){
# $1 is pid of process
# $2 is string to echo
string="$2"
local dots
local progress
dots=""
while [[ -d /proc/$1 ]]; do
dots="${dots}."
progress="$dots"
if [[ ${#dots} -gt "10" ]]; then
dots=""
progress=" "
fi
echo -ne " ${2}$progress\r"; /usr/bin/sleep 0.3
done
}
progstatus(){
# $1 is return status of process
# $2 is string to echo
# $3 line number function was called from
local tracestring
local pad
tracestring="${FUNCNAME[0]} called from ${FUNCNAME[1]} $3"
pad=$(printf -- ' %.0s' {1..80})
[ "$trace" == "yes" ] && printf '%.*s' 80 "${tracestring}${pad}" && echo ""
if [[ $1 == "0" ]]; then
echo -e "$2 "
else
ding
echo -e "Line ${LINENO}: ERROR $2 failed!"
echo "$tracestring ($scriptver)"
if [[ $exitonerror != "no" ]]; then
exit 1 # Skip exit if exitonerror != no
fi
fi
exitonerror=""
#echo "return: $1" # debug
}
package_status(){
# $1 is package name
[ "$trace" == "yes" ] && echo "${FUNCNAME[0]} called from ${FUNCNAME[1]}"
# local code
synopkg status "${1}" >/dev/null
code="$?"
# DSM 7.2 0 = started, 17 = stopped, 255 = not_installed, 150 = broken
# DSM 6 to 7.1 0 = started, 3 = stopped, 4 = not_installed, 150 = broken
if [[ $code == "0" ]]; then
#echo "$1 is started" # debug
return 0
elif [[ $code == "17" ]] || [[ $code == "3" ]]; then
#echo "$1 is stopped" # debug
return 1
elif [[ $code == "255" ]] || [[ $code == "4" ]]; then
#echo "$1 is not installed" # debug
return 255
elif [[ $code == "150" ]]; then
#echo "$1 is broken" # debug
return 150
else
return "$code"
fi
}
package_is_running(){
# $1 is package name
[ "$trace" == "yes" ] && echo "${FUNCNAME[0]} called from ${FUNCNAME[1]}"
synopkg is_onoff "${1}" >/dev/null
code="$?"
return "$code"
}
wait_status(){
# Wait for package to finish stopping or starting
# $1 is package
# $2 is start or stop
[ "$trace" == "yes" ] && echo "${FUNCNAME[0]} called from ${FUNCNAME[1]}"
local num
if [[ $2 == "start" ]]; then
state="0"
elif [[ $2 == "stop" ]]; then
state="1"
fi
if [[ $state == "0" ]] || [[ $state == "1" ]]; then
num="0"
package_status "$1"
while [[ $? != "$state" ]]; do
sleep 1
num=$((num +1))
if [[ $num -gt "20" ]]; then
break
fi
package_status "$1"
done
fi
}
package_stop(){
# $1 is package name
# $2 is package display name
[ "$trace" == "yes" ] && echo "${FUNCNAME[0]} called from ${FUNCNAME[1]}"
# Docker can take 12 minutes to stop 70 containers
timeout 30m synopkg stop "$1" >/dev/null &
pid=$!
string="Stopping ${2}"
progbar "$pid" "$string"
wait "$pid"
progstatus "$?" "$string" "line ${LINENO}"
# Allow package processes to finish stopping
wait_status "$1" stop &
pid=$!
string="Waiting for ${2} to stop"
progbar "$pid" "$string"
wait "$pid"
progstatus "$?" "$string" "line ${LINENO}"
}
stop_packages(){
# Check package is running
[ "$trace" == "yes" ] && echo "${FUNCNAME[0]} called from ${FUNCNAME[1]}"
if package_is_running "$pkg"; then
# Stop package
package_stop "$pkg" "$pkg_name"
# Check package stopped
if package_is_running "$pkg"; then
#stop_pkg_fail="yes"
ding
echo -e "Line ${LINENO}: ERROR Failed to stop ${pkg_name}!"
# echo "${pkg_name} status $code"
#process_error="yes"
return 1
else
echo "$pkg" >> "$shutdown_log"
#stop_pkg_fail=""
fi
if [[ $pkg == "ContainerManager" ]] || [[ $pkg == "Docker" ]]; then
# Stop containerd-shim
killall containerd-shim >/dev/null 2>&1
fi
# else
# skip_start="yes"
fi
}
skip_dev_tools(){
# $1 is $package
[ "$trace" == "yes" ] && echo "${FUNCNAME[0]} called from ${FUNCNAME[1]}"
local skip1
local skip2
skip1="$(synogetkeyvalue "/var/packages/${package}/INFO" startable)"
skip2="$(synogetkeyvalue "/var/packages/${package}/INFO" ctl_stop)"
if [[ $skip1 == "no" ]] || [[ $skip2 == "no" ]]; then
return 0
else
return 1
fi
}
# Get list of volumes with shared folders
readarray -t shares_array < <(synoshare --enum | tail -n +5)
for share in "${shares_array[@]}"; do
#echo "share: $share" # debug ################################################
if [[ $buildnumber -gt "64570" ]]; then
# DSM 7.2.1 and later
# synoshare --get-real-path is case insensitive
path="$(synoshare --get-real-path "$share" | cut -d"/" -f2)"
else
# DSM 7.2 and earlier
# synoshare --getmap is case insensitive
path="$(synoshare --getmap "$share" | grep volume | cut -d"[" -f2 | cut -d"]" -f1)"
# I could also have used:
# web_pkg_path=$(/usr/syno/sbin/synoshare --get "$share" | tr '[]' '\n' | sed -n "9p")
fi
# Ignore external USB and eSATA volumes
if echo "$path" | grep -q -E 'volume[0-9]'; then
volumes_array+=("$path")
fi
done
#echo -e "\nvolumes_array:" # debug ##################
#for v in "${volumes_array[@]}"; do echo "$v"; done # debug ##################
#echo -e "\n" # debug ##################
# Sort array to remove duplicates
IFS=$'\n'
volumes_array_sorted=($(sort -u <<<"${volumes_array[*]}"))
unset IFS
#echo -e "\nvolumes_array_sorted:" # debug ###########
#for v in "${volumes_array_sorted[@]}"; do echo "$v"; done # debug ###########
#echo -e "\n" # debug ###########
#exit
# Check there is an NVMe volume
nvme_vols=()
for vol in "${volumes_array_sorted[@]}"; do
#echo "$vol" # debug #########################################################
vg=$(lvdisplay | grep /volume_"${vol#volume}" | cut -d"/" -f3)
md=$(pvdisplay | grep -B 1 -E '[ ]'"$vg" | grep /dev/ | cut -d"/" -f3)
if cat /proc/mdstat | grep "$md" | grep nvme >/dev/null; then
nvme_vols_qty=$((nvme_vols_qty +1))
nvme_vols+=("$vol")
fi
done
if [[ $nvme_vols_qty -lt "0" ]]; then
ding
echo -e "No NVMe volumes found."
exit 4 # Script not needed
fi
#echo "nvme_vols_qty $nvme_vols_qty" # debug ######################
#for v in "${nvme_vols[@]}"; do echo "$v"; done # debug ######################
#echo "" # debug ######################
#exit
# Get list of packages installed on NVMe volume
if ! cd /var/packages; then
ding
echo -e "Failed to cd to /var/packages!"
exit 5 # Failed to cd to /var/packages
fi
declare -A package_names
declare -A package_names_rev
#package_infos=( )
while IFS= read -r -d '' link && IFS= read -r -d '' target; do
if [[ ${link##*/} == "target" ]] && echo "$target" | grep -q 'volume'; then
# Check symlink target exists
if [[ -a "/var/packages${link#.}" ]] ; then
# Skip broken packages with no INFO file
package="$(printf %s "$link" | cut -d'/' -f2 )"
if [[ -f "/var/packages/${package}/INFO" ]]; then
package_volume="$(printf %s "$target" | cut -d'/' -f1,2 )"
#echo "package_volume: $package_volume" # debug ###############################
# Check if package is on NVMe volume
# shellcheck disable=SC2076
if [[ "/${nvme_vols[*]}" =~ "$package_volume" ]]; then
package_name="$(synogetkeyvalue "/var/packages/${package}/INFO" displayname)"
if [[ -z "$package_name" ]]; then
package_name="$(synogetkeyvalue "/var/packages/${package}/INFO" package)"
fi
#echo "package_name: $package_name" # debug ###############################
# Skip packages that are dev tools with no data
if ! skip_dev_tools "$package"; then
#package_infos+=("${package_volume}|${package_name}")
package_names["${package_name}"]="${package}"
package_names_rev["${package}"]="${package_name}"
fi
fi
fi
fi
fi
done < <(find . -maxdepth 2 -type l -printf '%p\0%l\0')
#echo -e "\npackage_infos:"
#for p in "${package_infos[@]}"; do echo "$p"; done
#echo -e "\npackage_names:"
#for p in "${package_names[@]}"; do echo "$p"; done
#echo -e "\npackage_names_rev:"
#for p in "${package_names_rev[@]}"; do echo "$p"; done
# Loop through pkgs_sorted array and process package
for pkg in "${package_names[@]}"; do
pkg_name="${package_names_rev["$pkg"]}"
#process_error=""
stop_packages
#echo "stop_package: $pkg" # debug ############################################
done