#!/usr/bin/env bash set -euo pipefail WORK_DIR="${WORK_DIR:-/opt/app}" PURGE_DATA="${PURGE_DATA:-0}" # Keep this list in sync with install_iot.sh and install_efka.sh. # Format: # "service_name|download_url|start_command|stop_command|dependency_dirs|service_user|service_group" INSTALL_ITEMS=( "iot|https://punchsky.cn/down/iot-0.1.0.tar.gz|{install_dir}/bin/iot foreground|{install_dir}/bin/iot stop|/var/lib/iot/mnesia,/var/lib/endpoint/database,/var/lib/endpoint/endpoint_log" "efka|https://punchsky.cn/down/efka-0.1.0.tar.gz|{install_dir}/bin/efka foreground|{install_dir}/bin/efka stop|/var/lib/efka/mnesia,/var/lib/efka/dets,/var/lib/efka/docker" ) usage() { cat <<'EOF' Usage: sudo ./uninstall_service.sh [options] Options: -w, --work-dir Working directory used during installation. Default: /opt/app. --purge-data Also remove dependency/data directories from INSTALL_ITEMS. -h, --help Show this help. Default behavior: - Stop services in reverse INSTALL_ITEMS order. - Disable services from boot. - Remove /etc/systemd/system/.service and service backups. - Run systemctl daemon-reload and reset-failed. - Remove downloaded archives and service-name program directories under WORK_DIR. - Remove /usr/local/bin/iot_ctrl. - Keep dependency/data directories unless --purge-data is passed. EOF } log() { printf '[uninstall] %s\n' "$*" } fail() { printf '[uninstall][error] %s\n' "$*" >&2 exit 1 } require_command() { command -v "$1" >/dev/null 2>&1 || fail "Missing required command: $1" } require_option_value() { local option="$1" local value="${2:-}" [[ -n "$value" ]] || fail "$option requires a value" } require_root_runner() { if [[ "${EUID}" -ne 0 ]]; then require_command sudo fi } run_as_root() { if [[ "${EUID}" -eq 0 ]]; then "$@" else sudo "$@" fi } parse_args() { while [[ $# -gt 0 ]]; do case "$1" in -w|--work-dir) require_option_value "$1" "${2:-}" WORK_DIR="${2:-}" shift 2 ;; --purge-data) PURGE_DATA=1 shift ;; -h|--help) usage exit 0 ;; *) fail "Unknown argument: $1" ;; esac done } validate_args() { [[ -n "$WORK_DIR" ]] || fail "--work-dir is required" [[ "$WORK_DIR" = /* ]] || fail "--work-dir must be an absolute path" set +u local item_count="${#INSTALL_ITEMS[@]}" set -u [[ "$item_count" -gt 0 ]] || fail "No programs configured. Fill INSTALL_ITEMS inside this script" } archive_name_from_url() { local download_url="$1" local clean_url="$download_url" clean_url="${clean_url%%\?*}" clean_url="${clean_url%%#*}" basename "$clean_url" } replace_placeholders() { local value="$1" local install_dir="$2" local service_name="$3" value="${value//\{install_dir\}/$install_dir}" value="${value//\{work_dir\}/$WORK_DIR}" value="${value//\{service_name\}/$service_name}" printf '%s\n' "$value" } service_name_from_item() { local item="$1" local service_name IFS='|' read -r service_name _ <<< "$item" printf '%s\n' "$service_name" } stop_and_disable_services() { local index local service_name set +u for ((index=${#INSTALL_ITEMS[@]} - 1; index >= 0; index--)); do set -u service_name="$(service_name_from_item "${INSTALL_ITEMS[$index]}")" [[ -n "$service_name" ]] || continue log "Stopping service: ${service_name}.service" run_as_root systemctl stop "${service_name}.service" || log "Service not running or not found: ${service_name}.service" log "Disabling service on boot: ${service_name}.service" run_as_root systemctl disable "${service_name}.service" || log "Service not enabled or not found: ${service_name}.service" set +u done set -u } remove_service_files() { local item local service_name local service_file set +u for item in "${INSTALL_ITEMS[@]}"; do set -u service_name="$(service_name_from_item "$item")" [[ -n "$service_name" ]] || continue service_file="/etc/systemd/system/${service_name}.service" log "Removing systemd service file: $service_file" run_as_root rm -f "$service_file" log "Removing systemd service backups: ${service_file}.bak.*" run_as_root sh -c 'rm -f "$1".bak.*' sh "$service_file" set +u done set -u } remove_program_files() { local item local service_name local download_url local start_command local stop_command local dependency_dirs local service_user local service_group local archive_name local archive_path local install_dir set +u for item in "${INSTALL_ITEMS[@]}"; do set -u IFS='|' read -r service_name download_url start_command stop_command dependency_dirs service_user service_group <<< "$item" [[ -n "$service_name" ]] || continue [[ -n "$download_url" ]] || fail "Invalid package config for $service_name: download URL is empty" archive_name="$(archive_name_from_url "$download_url")" [[ -n "$archive_name" ]] || fail "Cannot parse archive filename from URL: $download_url" archive_path="${WORK_DIR}/${archive_name}" install_dir="${WORK_DIR}/${service_name}" log "Removing downloaded archive: $archive_path" run_as_root rm -f "$archive_path" log "Removing installed program directory: $install_dir" run_as_root rm -rf "$install_dir" if [[ "$PURGE_DATA" = "1" ]]; then remove_dependency_dirs "$dependency_dirs" "$install_dir" "$service_name" fi set +u done set -u } remove_global_links() { local link_path="/usr/local/bin/iot_ctrl" log "Removing global command link: $link_path" run_as_root rm -f "$link_path" } remove_dependency_dirs() { local dependency_dirs="$1" local install_dir="$2" local service_name="$3" local dependency_dir local dependency_dir_list local -a dependency_dir_array [[ -n "$dependency_dirs" ]] || return 0 dependency_dir_list="$(replace_placeholders "$dependency_dirs" "$install_dir" "$service_name")" IFS=',' read -ra dependency_dir_array <<< "$dependency_dir_list" for dependency_dir in "${dependency_dir_array[@]}"; do [[ -n "$dependency_dir" ]] || continue [[ "$dependency_dir" = /* ]] || fail "Dependency directory must be an absolute path: $dependency_dir" log "Removing dependency/data directory: $dependency_dir" run_as_root rm -rf "$dependency_dir" done } reload_systemd() { log "Reloading systemd" run_as_root systemctl daemon-reload log "Resetting failed systemd state" run_as_root systemctl reset-failed || true } main() { parse_args "$@" validate_args require_root_runner require_command systemctl stop_and_disable_services remove_service_files reload_systemd remove_global_links remove_program_files log "Uninstall completed" } main "$@"