From 2fd88401f5b81bcabe9a9b3e783de52dcfeea16a Mon Sep 17 00:00:00 2001 From: anlicheng <244108715@qq.com> Date: Sat, 30 May 2026 23:35:37 +0800 Subject: [PATCH] fix config --- install_service.sh | 229 ++++++++++++++++++++++++++++-------- uninstall_service.sh | 271 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 451 insertions(+), 49 deletions(-) create mode 100755 uninstall_service.sh diff --git a/install_service.sh b/install_service.sh index 745df11..243b7e5 100755 --- a/install_service.sh +++ b/install_service.sh @@ -10,17 +10,40 @@ CURRENT_USER="${SUDO_USER:-$(id -un)}" SERVICE_USER="${SERVICE_USER:-$CURRENT_USER}" SERVICE_GROUP="${SERVICE_GROUP:-$(id -gn "$SERVICE_USER")}" -# Configure multiple programs here before publishing this script. +# Configure programs here before publishing this script. # Format: -# "service_name|download_url|start_command|stop_command|dependency_dirs|service_user|service_group" +# INSTALL_=( +# "service_name" +# "download_url" +# "start_command" +# "stop_command" +# "service_user" +# "service_group" +# ) +# +# Environment variables are rendered into the corresponding systemd service +# file by service_env_lines(), using the [prod] sections from iot/env.file and +# efka/env.file. Directory creation is driven by those service env values. # # Use {install_dir}, {work_dir} and {service_name} in commands and directories. -# dependency_dirs is a comma-separated list. dependency_dirs, service_user and -# service_group are optional. +# service_user and service_group are optional. -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" +INSTALL_IOT=( + "iot" + "https://punchsky.cn/down/iot-0.1.0.tar.gz" + "{install_dir}/bin/iot foreground" + "{install_dir}/bin/iot stop" + "" + "" +) + +INSTALL_EFKA=( + "efka" + "https://punchsky.cn/down/efka-0.1.0.tar.gz" + "{install_dir}/bin/efka foreground" + "{install_dir}/bin/efka stop" + "" + "" ) TEMP_FILES=() INSTALLED_SERVICES=() @@ -43,13 +66,18 @@ Options: -h, --help Show this help. Array config inside script: - INSTALL_ITEMS=( - "iot|https://punchsky.cn/down/iot-0.1.0.tar.gz|{install_dir}/bin/iot foreground|{install_dir}/bin/iot stop|{work_dir}/var/lib/iot/mnesia,{work_dir}/var/log/iot,{work_dir}/var/run/iot" - "efka|https://punchsky.cn/down/efka-0.1.0.tar.gz|{install_dir}/bin/efka foreground|{install_dir}/bin/efka stop|{work_dir}/var/lib/efka/mnesia,{work_dir}/var/log/efka,{work_dir}/var/run/efka" - ) + INSTALL_IOT + INSTALL_EFKA Package format: - "service_name|download_url|start_command|stop_command|dependency_dirs|service_user|service_group" + INSTALL_=( + "service_name" + "download_url" + "start_command" + "stop_command" + "service_user" + "service_group" + ) Placeholders: {install_dir} will be replaced with the extracted package directory. @@ -148,11 +176,8 @@ 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" + require_install_config INSTALL_IOT + require_install_config INSTALL_EFKA } prepare_work_dir() { @@ -167,6 +192,7 @@ render_service_file() { local install_dir="$4" local start_command="$5" local stop_command="$6" + local env_line cat <.service and service backups. + - Run systemctl daemon-reload and reset-failed. + - Remove downloaded archives and extracted program directories under WORK_DIR. + - 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" +} + +strip_archive_suffix() { + local filename="$1" + + case "$filename" in + *.tar.gz) printf '%s\n' "${filename%.tar.gz}" ;; + *.tgz) printf '%s\n' "${filename%.tgz}" ;; + *) fail "Unsupported archive suffix: $filename. Expected .tar.gz or .tgz" ;; + esac +} + +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 app_dir_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" + + app_dir_name="$(strip_archive_suffix "$archive_name")" + archive_path="${WORK_DIR}/${archive_name}" + install_dir="${WORK_DIR}/${app_dir_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_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_program_files + + log "Uninstall completed" +} + +main "$@"