474 lines
16 KiB
Bash
Executable File
474 lines
16 KiB
Bash
Executable File
#!/usr/bin/env bash
|
||
set -Eeuo pipefail
|
||
|
||
# TallyNote native uninstaller. The default operation removes only the
|
||
# application and service integration; the database and attachments stay in
|
||
# place until --purge-data --yes is explicitly requested.
|
||
PATH=/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/bin
|
||
export PATH
|
||
umask 077
|
||
|
||
TEST_MODE=${TALLYNOTE_UNINSTALL_TEST_MODE:-false}
|
||
TEST_ROOT=${TALLYNOTE_UNINSTALL_ROOT:-}
|
||
PREFIX=${TALLYNOTE_PREFIX:-/opt/tallynote}
|
||
DATA_DIR=${TALLYNOTE_DATA_DIR:-/var/lib/tallynote}
|
||
CONFIG_DIR=${TALLYNOTE_CONFIG_DIR:-/etc/tallynote}
|
||
UNIT_DIR=${TALLYNOTE_SYSTEMD_UNIT_DIR:-/etc/systemd/system}
|
||
SBIN_DIR=${TALLYNOTE_SBIN_DIR:-/usr/local/sbin}
|
||
LIBEXEC_DIR=${TALLYNOTE_LIBEXEC_DIR:-/usr/local/libexec}
|
||
SYSTEMCTL_BIN=systemctl
|
||
SYSTEMCTL_AVAILABLE=0
|
||
PURGE_DATA=0
|
||
PURGE_CONFIG=0
|
||
YES=0
|
||
DRY_RUN=0
|
||
FORCE=0
|
||
EXPLICIT_PREFIX=0
|
||
EXPLICIT_DATA=0
|
||
EXPLICIT_CONFIG=0
|
||
|
||
die() { printf 'tallynote uninstaller: %s\n' "$*" >&2; exit 1; }
|
||
log() { printf 'tallynote uninstaller: %s\n' "$*"; }
|
||
|
||
usage() {
|
||
cat <<'EOF'
|
||
Usage: tallynote-uninstall [--yes] [--purge-data] [--purge-config]
|
||
[--dry-run] [--force]
|
||
[--prefix PATH] [--data-dir PATH] [--config-dir PATH]
|
||
|
||
By default, remove the TallyNote release tree, systemd units, update helpers,
|
||
and known configuration files. The database, attachments, staging, exports,
|
||
update queue, and update backups are preserved. Data removal requires both
|
||
--purge-data and --yes. --force is only for an operator who has verified that
|
||
no update is in progress; it overrides the pending-update guard.
|
||
EOF
|
||
}
|
||
|
||
is_true() { [[ "$1" == true || "$1" == 1 ]]; }
|
||
|
||
if [[ "$TEST_MODE" != true && "$TEST_MODE" != false && "$TEST_MODE" != 1 && "$TEST_MODE" != 0 ]]; then
|
||
die 'TALLYNOTE_UNINSTALL_TEST_MODE must be true or false'
|
||
fi
|
||
if [[ "$TEST_MODE" == 1 ]]; then TEST_MODE=true; fi
|
||
if [[ "$TEST_MODE" == 0 ]]; then TEST_MODE=false; fi
|
||
|
||
while (($#)); do
|
||
case "$1" in
|
||
--yes) YES=1 ;;
|
||
--purge-data) PURGE_DATA=1 ;;
|
||
--purge-config) PURGE_CONFIG=1 ;;
|
||
--dry-run) DRY_RUN=1 ;;
|
||
--force) FORCE=1 ;;
|
||
--prefix) PREFIX=${2:?missing value for --prefix}; EXPLICIT_PREFIX=1; shift ;;
|
||
--data-dir) DATA_DIR=${2:?missing value for --data-dir}; EXPLICIT_DATA=1; shift ;;
|
||
--config-dir) CONFIG_DIR=${2:?missing value for --config-dir}; EXPLICIT_CONFIG=1; shift ;;
|
||
-h|--help) usage; exit 0 ;;
|
||
*) die "unknown option: $1" ;;
|
||
esac
|
||
shift
|
||
done
|
||
|
||
if [[ "$TEST_MODE" == true ]]; then
|
||
[[ -n "$TEST_ROOT" ]] || die 'test mode requires TALLYNOTE_UNINSTALL_ROOT'
|
||
[[ "$TEST_ROOT" = /* && "$TEST_ROOT" != *'..'* && "$TEST_ROOT" != *'//'* && "$TEST_ROOT" != *$'\n'* && "$TEST_ROOT" != *$'\r'* ]] || die 'test root is invalid'
|
||
(( EXPLICIT_PREFIX )) || PREFIX=${TALLYNOTE_PREFIX:-$TEST_ROOT/opt/tallynote}
|
||
(( EXPLICIT_DATA )) || DATA_DIR=${TALLYNOTE_DATA_DIR:-$TEST_ROOT/var/lib/tallynote}
|
||
(( EXPLICIT_CONFIG )) || CONFIG_DIR=${TALLYNOTE_CONFIG_DIR:-$TEST_ROOT/etc/tallynote}
|
||
UNIT_DIR=${TALLYNOTE_SYSTEMD_UNIT_DIR:-$TEST_ROOT/etc/systemd/system}
|
||
SBIN_DIR=${TALLYNOTE_SBIN_DIR:-$TEST_ROOT/usr/local/sbin}
|
||
LIBEXEC_DIR=${TALLYNOTE_LIBEXEC_DIR:-$TEST_ROOT/usr/local/libexec}
|
||
SYSTEMCTL_BIN=${TALLYNOTE_SYSTEMCTL_BIN:-systemctl}
|
||
fi
|
||
|
||
stat_uid() { stat -c '%u' "$1" 2>/dev/null || stat -f '%u' "$1"; }
|
||
stat_mode() { stat -c '%a' "$1" 2>/dev/null || stat -f '%Lp' "$1"; }
|
||
stat_mode_bits() {
|
||
local mode
|
||
mode=$(stat_mode "$1")
|
||
[[ "$mode" =~ ^[0-7]+$ ]] || die "无法读取路径权限:$1"
|
||
printf '%d' "$((8#$mode))"
|
||
}
|
||
|
||
allowed_owner() {
|
||
local path=$1 uid
|
||
uid=$(stat_uid "$path")
|
||
if [[ "$TEST_MODE" == true ]]; then
|
||
[[ "$uid" == "$(id -u)" || "$uid" == 0 ]]
|
||
else
|
||
[[ "$uid" == 0 ]]
|
||
fi
|
||
}
|
||
|
||
allowed_data_owner() {
|
||
local path=$1 uid tallynote_uid
|
||
uid=$(stat_uid "$path")
|
||
if [[ "$TEST_MODE" == true ]]; then
|
||
[[ "$uid" == "$(id -u)" || "$uid" == 0 ]]
|
||
return
|
||
fi
|
||
[[ "$uid" == 0 ]] && return 0
|
||
tallynote_uid=$(id -u tallynote 2>/dev/null || true)
|
||
[[ -n "$tallynote_uid" && "$uid" == "$tallynote_uid" ]]
|
||
}
|
||
|
||
validate_path_value() {
|
||
local value=$1 label=$2
|
||
[[ "$value" = /* && "$value" != *$'\n'* && "$value" != *$'\r'* ]] || die "$label 必须是绝对路径"
|
||
[[ "$value" =~ ^/[A-Za-z0-9._/-]+$ && "$value" != *"//"* && "$value" != *"/../"* && "$value" != */.. && "$value" != *"/./"* && "$value" != */. && "$value" != / && "$value" != */ ]] || die "$label 包含不受支持的路径字符"
|
||
case "$value" in
|
||
/opt|/var|/etc|/usr|/usr/local|/bin|/sbin|/home|/root|/tmp) die "$label 不能指向系统顶层目录" ;;
|
||
esac
|
||
}
|
||
|
||
validate_parent_chain() {
|
||
local target=$1 current=/ component relative
|
||
relative=${target#/}
|
||
IFS='/' read -r -a _parts <<< "$relative"
|
||
for component in "${_parts[@]}"; do
|
||
[[ -n "$component" ]] || continue
|
||
current="${current%/}/$component"
|
||
if [[ -L "$current" ]]; then die "路径不能包含符号链接:$current"; fi
|
||
if [[ -e "$current" ]]; then
|
||
[[ -d "$current" ]] || die "路径不是目录:$current"
|
||
allowed_owner "$current" || die "路径目录的所有者不受信任:$current"
|
||
local mode_bits
|
||
mode_bits=$(stat_mode_bits "$current")
|
||
(( (mode_bits & 18) == 0 || (mode_bits & 512) != 0 )) || die "路径目录权限过宽:$current"
|
||
fi
|
||
done
|
||
}
|
||
|
||
validate_target() {
|
||
local target=$1 label=$2 owner_check=allowed_owner
|
||
[[ "${3:-}" == data ]] && owner_check=allowed_data_owner
|
||
validate_path_value "$target" "$label"
|
||
validate_parent_chain "$target"
|
||
if [[ -e "$target" || -L "$target" ]]; then
|
||
"$owner_check" "$target" || die "$label 的所有者不受信任:$target"
|
||
fi
|
||
}
|
||
|
||
read_env_value() {
|
||
local file=$1 key=$2
|
||
sed -n "s/^${key}=//p" "$file" | head -n 1
|
||
}
|
||
|
||
env_key_count() {
|
||
local file=$1 key=$2
|
||
awk -v key="$key" 'index($0, key "=") == 1 { count += 1 } END { print count + 0 }' "$file"
|
||
}
|
||
|
||
load_config() {
|
||
local env_file=$CONFIG_DIR/tallynote.env value key count
|
||
[[ -e "$env_file" || -L "$env_file" ]] || return 0
|
||
[[ -f "$env_file" && ! -L "$env_file" ]] || die '环境文件不是普通文件'
|
||
allowed_owner "$env_file" || die '环境文件的所有者不受信任'
|
||
local mode_bits
|
||
mode_bits=$(stat_mode_bits "$env_file")
|
||
(( (mode_bits & 18) == 0 )) || die '环境文件不能被组或其他用户写入'
|
||
for key in TALLYNOTE_INSTALL_PREFIX TALLYNOTE_DATA_DIR; do
|
||
count=$(env_key_count "$env_file" "$key")
|
||
[[ "$count" == 0 || "$count" == 1 ]] || die "环境文件包含重复配置:$key"
|
||
done
|
||
if (( ! EXPLICIT_PREFIX )); then
|
||
value=$(read_env_value "$env_file" TALLYNOTE_INSTALL_PREFIX)
|
||
[[ -z "$value" ]] || PREFIX=$value
|
||
fi
|
||
if (( ! EXPLICIT_DATA )); then
|
||
value=$(read_env_value "$env_file" TALLYNOTE_DATA_DIR)
|
||
[[ -z "$value" ]] || DATA_DIR=$value
|
||
fi
|
||
}
|
||
|
||
path_inside() {
|
||
local child=$1 parent=$2
|
||
[[ "$child" == "$parent"/* ]]
|
||
}
|
||
|
||
assert_disjoint_paths() {
|
||
local left left_label right right_label
|
||
local -a labels=(prefix data config unit sbin libexec)
|
||
for left_label in "${labels[@]}"; do
|
||
case "$left_label" in
|
||
prefix) left=$PREFIX ;;
|
||
data) left=$DATA_DIR ;;
|
||
config) left=$CONFIG_DIR ;;
|
||
unit) left=$UNIT_DIR ;;
|
||
sbin) left=$SBIN_DIR ;;
|
||
libexec) left=$LIBEXEC_DIR ;;
|
||
esac
|
||
for right_label in "${labels[@]}"; do
|
||
[[ "$left_label" == "$right_label" ]] && continue
|
||
case "$right_label" in
|
||
prefix) right=$PREFIX ;;
|
||
data) right=$DATA_DIR ;;
|
||
config) right=$CONFIG_DIR ;;
|
||
unit) right=$UNIT_DIR ;;
|
||
sbin) right=$SBIN_DIR ;;
|
||
libexec) right=$LIBEXEC_DIR ;;
|
||
esac
|
||
if [[ "$left" == "$right" ]] || path_inside "$left" "$right" || path_inside "$right" "$left"; then
|
||
die "卸载目录不能互相嵌套:$left 与 $right"
|
||
fi
|
||
done
|
||
done
|
||
}
|
||
|
||
assert_test_scope() {
|
||
[[ "$TEST_MODE" == true ]] || return 0
|
||
[[ -d "$TEST_ROOT" && ! -L "$TEST_ROOT" ]] || die 'test root must be an existing directory'
|
||
validate_parent_chain "$TEST_ROOT"
|
||
allowed_owner "$TEST_ROOT" || die 'test root owner is not trusted'
|
||
local value label
|
||
for label in PREFIX DATA_DIR CONFIG_DIR UNIT_DIR SBIN_DIR LIBEXEC_DIR; do
|
||
case "$label" in
|
||
PREFIX) value=$PREFIX ;;
|
||
DATA_DIR) value=$DATA_DIR ;;
|
||
CONFIG_DIR) value=$CONFIG_DIR ;;
|
||
UNIT_DIR) value=$UNIT_DIR ;;
|
||
SBIN_DIR) value=$SBIN_DIR ;;
|
||
LIBEXEC_DIR) value=$LIBEXEC_DIR ;;
|
||
esac
|
||
[[ "$value" == "$TEST_ROOT"/* ]] || die "test mode path escapes TALLYNOTE_UNINSTALL_ROOT: $value"
|
||
done
|
||
}
|
||
|
||
managed_file() {
|
||
local target=$1 label=$2
|
||
case "$label" in
|
||
service\ unit|updater\ unit|path\ unit|update\ helper|update\ runner|uninstaller)
|
||
grep -Eiq 'tallynote|TallyNote' "$target" || return 1
|
||
if [[ "$label" == 'path unit' ]]; then
|
||
grep -Fq "$DATA_DIR" "$target" || return 1
|
||
elif [[ "$label" == *unit ]]; then
|
||
grep -Fq "$PREFIX" "$target" || return 1
|
||
else
|
||
grep -Eq 'TALLYNOTE_INSTALL_PREFIX|/opt/tallynote' "$target" || return 1
|
||
fi
|
||
;;
|
||
environment\ file)
|
||
grep -q '^TALLYNOTE_INSTALL_PREFIX=' "$target" || return 1
|
||
grep -q '^TALLYNOTE_DATA_DIR=' "$target" || return 1
|
||
[[ "$(read_env_value "$target" TALLYNOTE_INSTALL_PREFIX)" == "$PREFIX" ]] || return 1
|
||
[[ "$(read_env_value "$target" TALLYNOTE_DATA_DIR)" == "$DATA_DIR" ]] || return 1
|
||
;;
|
||
update\ public\ key)
|
||
[[ -f "$CONFIG_DIR/tallynote.env" ]] || return 1
|
||
[[ "$(read_env_value "$CONFIG_DIR/tallynote.env" TALLYNOTE_UPDATE_PUBLIC_KEY_FILE)" == "$target" ]] || return 1
|
||
;;
|
||
*) return 0 ;;
|
||
esac
|
||
}
|
||
|
||
validate_release_tree() {
|
||
local tree=$1 owner_check=${2:-allowed_owner}
|
||
[[ -d "$tree" && ! -L "$tree" ]] || die "发布目录无效:$tree"
|
||
"$owner_check" "$tree" || die "发布目录的所有者不受信任:$tree"
|
||
if find "$tree" -type l -print -quit | grep -q .; then
|
||
die "发布目录包含符号链接:$tree"
|
||
fi
|
||
if find "$tree" ! -type d ! -type f -print -quit | grep -q .; then
|
||
die "发布目录包含不支持的文件类型:$tree"
|
||
fi
|
||
local node mode_bits
|
||
while IFS= read -r node; do
|
||
"$owner_check" "$node" || die "发布目录节点的所有者不受信任:$node"
|
||
mode_bits=$(stat_mode_bits "$node")
|
||
(( (mode_bits & 18) == 0 )) || die "发布目录节点权限过宽:$node"
|
||
done < <(find "$tree" -print)
|
||
}
|
||
|
||
pending_update() {
|
||
[[ -e "$PREFIX/.update-state" || -L "$PREFIX/.update-state" || -e "$DATA_DIR/update-request.json" || -L "$DATA_DIR/update-request.json" ]]
|
||
}
|
||
|
||
run_systemctl() {
|
||
(( DRY_RUN )) && return 0
|
||
if [[ "$SYSTEMCTL_BIN" == */* ]]; then
|
||
[[ -x "$SYSTEMCTL_BIN" ]] || return 0
|
||
else
|
||
command -v "$SYSTEMCTL_BIN" >/dev/null 2>&1 || return 0
|
||
fi
|
||
"$SYSTEMCTL_BIN" "$@"
|
||
}
|
||
|
||
stop_services() {
|
||
local unit active status
|
||
if (( DRY_RUN )); then
|
||
log 'dry-run: would stop/disable systemd units in path -> updater -> app order'
|
||
return 0
|
||
fi
|
||
if (( ! SYSTEMCTL_AVAILABLE )); then
|
||
for unit in tallynote-update.path tallynote-update.service tallynote.service; do
|
||
[[ ! -e "$UNIT_DIR/$unit" ]] || die 'systemctl 不可用,无法安全停止已安装服务'
|
||
done
|
||
return 0
|
||
fi
|
||
for unit in tallynote-update.path tallynote-update.service tallynote.service; do
|
||
active=0
|
||
if run_systemctl is-active --quiet "$unit" >/dev/null 2>&1; then
|
||
active=1
|
||
else
|
||
status=$?
|
||
case "$status" in
|
||
3|4) ;;
|
||
*) die "无法读取服务状态:$unit" ;;
|
||
esac
|
||
fi
|
||
if (( active )); then
|
||
run_systemctl stop "$unit" || die "无法停止服务:$unit"
|
||
fi
|
||
if [[ -e "$UNIT_DIR/$unit" ]]; then
|
||
run_systemctl disable "$unit" >/dev/null 2>&1 || die "无法禁用服务:$unit"
|
||
fi
|
||
done
|
||
run_systemctl daemon-reload >/dev/null 2>&1 || die 'systemd daemon-reload 失败'
|
||
}
|
||
|
||
validate_systemctl() {
|
||
local resolved uid mode_bits
|
||
if [[ "$TEST_MODE" == true ]]; then
|
||
if [[ "$SYSTEMCTL_BIN" == */* && -x "$SYSTEMCTL_BIN" ]]; then
|
||
SYSTEMCTL_AVAILABLE=1
|
||
fi
|
||
return 0
|
||
fi
|
||
resolved=$(command -v systemctl 2>/dev/null || true)
|
||
if [[ -z "$resolved" ]]; then
|
||
SYSTEMCTL_AVAILABLE=0
|
||
return 0
|
||
fi
|
||
[[ -x "$resolved" && ! -L "$resolved" ]] || die 'systemctl 必须是可信的普通可执行文件'
|
||
uid=$(stat_uid "$resolved")
|
||
mode_bits=$(stat_mode_bits "$resolved")
|
||
[[ "$uid" == 0 && $((mode_bits & 18)) -eq 0 ]] || die 'systemctl 必须由 root 拥有且不可被其他用户写入'
|
||
SYSTEMCTL_BIN=$resolved
|
||
SYSTEMCTL_AVAILABLE=1
|
||
}
|
||
|
||
remove_file_if_owned() {
|
||
local target=$1 label=$2
|
||
[[ -e "$target" || -L "$target" ]] || return 0
|
||
if [[ -L "$target" || ! -f "$target" ]]; then
|
||
log "warning: 保留非普通文件:$target"
|
||
return 0
|
||
fi
|
||
if ! allowed_owner "$target"; then
|
||
log "warning: 保留非本安装创建的文件:$target"
|
||
return 0
|
||
fi
|
||
if ! managed_file "$target" "$label"; then
|
||
log "warning: 保留内容不匹配的文件:$target"
|
||
return 0
|
||
fi
|
||
if (( DRY_RUN )); then
|
||
log "dry-run: remove $label $target"
|
||
else
|
||
rm -f -- "$target"
|
||
fi
|
||
}
|
||
|
||
remove_tree() {
|
||
local target=$1 label=$2 owner_check=${3:-allowed_owner}
|
||
[[ -e "$target" || -L "$target" ]] || return 0
|
||
[[ -d "$target" && ! -L "$target" ]] || die "$label 不是安全目录:$target"
|
||
"$owner_check" "$target" || die "$label 的所有者不受信任:$target"
|
||
validate_release_tree "$target" "$owner_check"
|
||
if (( DRY_RUN )); then
|
||
log "dry-run: remove $label $target"
|
||
else
|
||
rm -rf -- "$target"
|
||
fi
|
||
}
|
||
|
||
remove_prefix() {
|
||
local current=$PREFIX/current current_target releases=$PREFIX/releases
|
||
if [[ -L "$current" ]]; then
|
||
current_target=$(readlink "$current")
|
||
[[ "$current_target" = "$PREFIX/releases/"* && "$current_target" != *'..'* ]] || die 'current 符号链接指向安装目录之外'
|
||
[[ -d "$current_target" && ! -L "$current_target" ]] || die 'current 目标不是安全目录'
|
||
if (( DRY_RUN )); then
|
||
log "dry-run: remove current link $current"
|
||
else
|
||
rm -f -- "$current"
|
||
fi
|
||
elif [[ -e "$current" ]]; then
|
||
log "warning: 保留非符号链接 current:$current"
|
||
fi
|
||
remove_tree "$releases" 'releases'
|
||
remove_tree "$PREFIX/.update-work" 'update work'
|
||
remove_file_if_owned "$PREFIX/.update-state" 'update state'
|
||
if [[ -d "$PREFIX" && ! -L "$PREFIX" ]]; then
|
||
allowed_owner "$PREFIX" || die "安装目录的所有者不受信任:$PREFIX"
|
||
if (( DRY_RUN )); then
|
||
log "dry-run: remove empty install directory if empty: $PREFIX"
|
||
else
|
||
rmdir -- "$PREFIX" 2>/dev/null || true
|
||
fi
|
||
fi
|
||
}
|
||
|
||
remove_config() {
|
||
remove_file_if_owned "$CONFIG_DIR/update-signing-key.pub" 'update public key'
|
||
remove_file_if_owned "$CONFIG_DIR/tallynote.env" 'environment file'
|
||
if (( PURGE_CONFIG )) && [[ -d "$CONFIG_DIR" && ! -L "$CONFIG_DIR" ]]; then
|
||
allowed_owner "$CONFIG_DIR" || die '配置目录的所有者不受信任'
|
||
if (( DRY_RUN )); then log "dry-run: remove config directory if safe: $CONFIG_DIR"; else rmdir -- "$CONFIG_DIR" 2>/dev/null || true; fi
|
||
fi
|
||
}
|
||
|
||
remove_data() {
|
||
local backup_dir
|
||
backup_dir=$(dirname -- "$DATA_DIR")/tallynote-backups
|
||
if (( PURGE_DATA )); then
|
||
(( YES )) || die '--purge-data 必须同时提供 --yes'
|
||
remove_tree "$DATA_DIR" 'data' allowed_data_owner
|
||
remove_tree "$backup_dir" 'backup data'
|
||
else
|
||
log "保留数据目录:$DATA_DIR"
|
||
if [[ -d "$backup_dir" ]]; then
|
||
log "保留备份目录:$backup_dir"
|
||
fi
|
||
fi
|
||
return 0
|
||
}
|
||
|
||
main() {
|
||
if [[ "$TEST_MODE" != true ]]; then
|
||
[[ $EUID -eq 0 ]] || die '卸载必须以 root 运行(请使用 sudo)'
|
||
fi
|
||
if (( PURGE_DATA && ! YES )); then
|
||
die '--purge-data 必须同时提供 --yes'
|
||
fi
|
||
validate_path_value "$CONFIG_DIR" '配置目录'
|
||
validate_target "$CONFIG_DIR" '配置目录'
|
||
assert_test_scope
|
||
load_config
|
||
validate_target "$PREFIX" '安装目录'
|
||
validate_target "$DATA_DIR" '数据目录' data
|
||
validate_target "$CONFIG_DIR" '配置目录'
|
||
validate_target "$UNIT_DIR" 'systemd 单元目录'
|
||
validate_target "$SBIN_DIR" 'sbin 目录'
|
||
validate_target "$LIBEXEC_DIR" 'libexec 目录'
|
||
assert_test_scope
|
||
assert_disjoint_paths
|
||
validate_systemctl
|
||
if (( ! FORCE )) && pending_update; then
|
||
die '检测到未完成的更新状态;确认更新已停止后使用 --force 重试'
|
||
fi
|
||
log "target: prefix=$PREFIX data=$DATA_DIR config=$CONFIG_DIR"
|
||
stop_services
|
||
remove_prefix
|
||
remove_file_if_owned "$UNIT_DIR/tallynote.service" 'service unit'
|
||
remove_file_if_owned "$UNIT_DIR/tallynote-update.service" 'updater unit'
|
||
remove_file_if_owned "$UNIT_DIR/tallynote-update.path" 'path unit'
|
||
remove_file_if_owned "$SBIN_DIR/tallynote-update" 'update helper'
|
||
remove_file_if_owned "$LIBEXEC_DIR/tallynote-update-runner" 'update runner'
|
||
remove_file_if_owned "$SBIN_DIR/tallynote-uninstall" 'uninstaller'
|
||
remove_config
|
||
remove_data
|
||
log 'uninstall complete'
|
||
}
|
||
|
||
main "$@"
|