From c9c48edd425ef7927fc976c6a8c0d15a85b05501 Mon Sep 17 00:00:00 2001 From: anlicheng <244108715@qq.com> Date: Mon, 1 Jun 2026 14:37:42 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8B=86=E5=88=86=E7=8B=AC=E7=AB=8B=E7=9A=84?= =?UTF-8?q?=E5=AE=89=E8=A3=85=E7=A8=8B=E5=BA=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- install_service.sh => install_efka.sh | 52 +-- install_iot.sh | 505 ++++++++++++++++++++++++++ install_service.md | 105 +++--- uninstall_service.sh | 2 +- 4 files changed, 563 insertions(+), 101 deletions(-) rename install_service.sh => install_efka.sh (90%) create mode 100755 install_iot.sh diff --git a/install_service.sh b/install_efka.sh similarity index 90% rename from install_service.sh rename to install_efka.sh index fce5ec1..987e38d 100755 --- a/install_service.sh +++ b/install_efka.sh @@ -2,9 +2,9 @@ set -euo pipefail # Default config for: -# curl -fsSL https://punchsky.com/install | bash +# curl -fsSL https://punchsky.cn/install_efka.sh | bash # -# Fill these values before publishing this script at https://punchsky.com/install. +# Fill these values before publishing this script at https://punchsky.cn/install_efka.sh. WORK_DIR="${WORK_DIR:-/opt/app}" CURRENT_USER="${SUDO_USER:-$(id -un)}" SERVICE_USER="${SERVICE_USER:-$CURRENT_USER}" @@ -21,37 +21,14 @@ SERVICE_GROUP="${SERVICE_GROUP:-$(id -gn "$SERVICE_USER")}" # "service_group" # ) # -# IOT_ENV and EFKA_ENV are rendered into the corresponding systemd service -# file as Environment= lines, using the [prod] sections from iot/env.file and -# efka/env.file. Directory creation is driven by those service env values. -# IOT_DIR_ENV_KEYS and EFKA_DIR_ENV_KEYS list env keys whose values are +# EFKA_ENV is rendered into the systemd service file as Environment= lines, +# using the [prod] section from efka/env.file. Directory creation is driven by +# those service env values. EFKA_DIR_ENV_KEYS lists env keys whose values are # directories that must be created if they do not exist. # # Use {install_dir}, {work_dir} and {service_name} in commands and directories. # service_user and service_group are optional. -INSTALL_IOT=( - "iot" - "https://punchsky.cn/down/iot-0.1.0.tar.gz" - "{install_dir}/bin/iot foreground" - "{install_dir}/bin/iot stop" - "" - "" -) - -IOT_ENV=( - "IOT_API_URL=http://127.0.0.1/api/v1" - "ENDPOINT_ROOT_DIR=/var/lib/endpoint/database/" - "ENDPOINT_LOG_PATH=/var/lib/endpoint/endpoint_log" - "IOT_MNESIA_DIR=/var/lib/iot/mnesia/" -) - -IOT_DIR_ENV_KEYS=( - "ENDPOINT_ROOT_DIR" - "ENDPOINT_LOG_PATH" - "IOT_MNESIA_DIR" -) - INSTALL_EFKA=( "efka" "https://punchsky.cn/down/efka-0.1.0.tar.gz" @@ -82,9 +59,9 @@ INSTALLED_SERVICES=() usage() { cat <<'EOF' Usage: - curl -fsSL https://punchsky.cn/install | bash + curl -fsSL https://punchsky.cn/install_efka.sh | bash - curl -fsSL https://punchsky.cn/install | bash -s -- \ + curl -fsSL https://punchsky.cn/install_efka.sh | bash -s -- \ -w Config: @@ -97,11 +74,8 @@ Options: -h, --help Show this help. Array config inside script: - INSTALL_IOT INSTALL_EFKA - IOT_ENV EFKA_ENV - IOT_DIR_ENV_KEYS EFKA_DIR_ENV_KEYS Package format: @@ -120,7 +94,7 @@ Placeholders: {service_name} will be replaced with the service name. Example: - curl -fsSL https://punchsky.cn/install | bash + curl -fsSL https://punchsky.cn/install_efka.sh | bash EOF } @@ -211,7 +185,6 @@ validate_args() { [[ -n "$WORK_DIR" ]] || fail "--work-dir is required" [[ "$WORK_DIR" = /* ]] || fail "--work-dir must be an absolute path" - require_install_config INSTALL_IOT require_install_config INSTALL_EFKA } @@ -339,9 +312,6 @@ service_env_lines() { local env_line case "$service_name" in - iot) - env_array_name="IOT_ENV" - ;; efka) env_array_name="EFKA_ENV" ;; @@ -365,9 +335,6 @@ service_dir_env_keys() { local env_key case "$service_name" in - iot) - dir_keys_array_name="IOT_DIR_ENV_KEYS" - ;; efka) dir_keys_array_name="EFKA_DIR_ENV_KEYS" ;; @@ -497,7 +464,6 @@ install_program_from_config() { } install_programs() { - install_program_from_config INSTALL_IOT install_program_from_config INSTALL_EFKA } @@ -535,7 +501,7 @@ main() { reload_systemd enable_installed_services - log "Installation completed" + log "EFKA installation completed" } main "$@" diff --git a/install_iot.sh b/install_iot.sh new file mode 100755 index 0000000..25e8d30 --- /dev/null +++ b/install_iot.sh @@ -0,0 +1,505 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Default config for: +# curl -fsSL https://punchsky.cn/install_iot.sh | bash +# +# Fill these values before publishing this script at https://punchsky.cn/install_iot.sh. +WORK_DIR="${WORK_DIR:-/opt/app}" +CURRENT_USER="${SUDO_USER:-$(id -un)}" +SERVICE_USER="${SERVICE_USER:-$CURRENT_USER}" +SERVICE_GROUP="${SERVICE_GROUP:-$(id -gn "$SERVICE_USER")}" + +# Configure programs here before publishing this script. +# Format: +# INSTALL_=( +# "service_name" +# "download_url" +# "start_command" +# "stop_command" +# "service_user" +# "service_group" +# ) +# +# IOT_ENV is rendered into the systemd service file as Environment= lines, +# using the [prod] section from iot/env.file. Directory creation is driven by +# those service env values. IOT_DIR_ENV_KEYS lists env keys whose values are +# directories that must be created if they do not exist. +# +# Use {install_dir}, {work_dir} and {service_name} in commands and directories. +# service_user and service_group are optional. + +INSTALL_IOT=( + "iot" + "https://punchsky.cn/down/iot-0.1.0.tar.gz" + "{install_dir}/bin/iot foreground" + "{install_dir}/bin/iot stop" + "" + "" +) + +IOT_ENV=( + "IOT_API_URL=http://127.0.0.1/api/v1" + "ENDPOINT_ROOT_DIR=/var/lib/endpoint/database/" + "ENDPOINT_LOG_PATH=/var/lib/endpoint/endpoint_log" + "IOT_MNESIA_DIR=/var/lib/iot/mnesia/" +) + +IOT_DIR_ENV_KEYS=( + "ENDPOINT_ROOT_DIR" + "ENDPOINT_LOG_PATH" + "IOT_MNESIA_DIR" +) + +TEMP_FILES=() +INSTALLED_SERVICES=() + +usage() { + cat <<'EOF' +Usage: + curl -fsSL https://punchsky.cn/install_iot.sh | bash + + curl -fsSL https://punchsky.cn/install_iot.sh | bash -s -- \ + -w + +Config: + Configure programs inside this script before publishing it. + +Options: + -w, --work-dir Working directory used for installation. + --user Default service user. Default: current user. + --group Default service group. Default: current user's primary group. + -h, --help Show this help. + +Array config inside script: + INSTALL_IOT + IOT_ENV + IOT_DIR_ENV_KEYS + +Package format: + INSTALL_=( + "service_name" + "download_url" + "start_command" + "stop_command" + "service_user" + "service_group" + ) + +Placeholders: + {install_dir} will be replaced with the extracted package directory. + {work_dir} will be replaced with the root work directory. + {service_name} will be replaced with the service name. + +Example: + curl -fsSL https://punchsky.cn/install_iot.sh | bash +EOF +} + +log() { + printf '[install] %s\n' "$*" +} + +cleanup() { + local file + + set +u + for file in "${TEMP_FILES[@]}"; do + [[ -n "$file" ]] && rm -f "$file" + done + set -u +} + +fail() { + printf '[install][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 +} + +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 +} + +parse_args() { + while [[ $# -gt 0 ]]; do + case "$1" in + -w|--work-dir) + require_option_value "$1" "${2:-}" + WORK_DIR="${2:-}" + shift 2 + ;; + --user) + require_option_value "$1" "${2:-}" + SERVICE_USER="${2:-}" + shift 2 + ;; + --group) + require_option_value "$1" "${2:-}" + SERVICE_GROUP="${2:-}" + shift 2 + ;; + -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" + + require_install_config INSTALL_IOT +} + +prepare_work_dir() { + log "Creating root work directory: $WORK_DIR" + run_as_root install -d -m 0755 "$WORK_DIR" +} + +render_service_file() { + local service_name="$1" + local service_user="$2" + local service_group="$3" + local install_dir="$4" + local start_command="$5" + local stop_command="$6" + local env_line + + cat </dev/null +} + +validate_install_item() { + local service_name="$1" + local download_url="$2" + local start_command="$3" + + [[ -n "$service_name" ]] || fail "Invalid package config: service name is empty" + [[ -n "$download_url" ]] || fail "Invalid package config for $service_name: download URL is empty" + [[ -n "$start_command" ]] || fail "Invalid package config for $service_name: start command is empty" + [[ "$service_name" =~ ^[a-zA-Z0-9_.@-]+$ ]] || fail "Invalid service name: $service_name" +} + +require_install_config() { + local config_name="$1" + local config_count + local service_name + local download_url + local start_command + + eval "config_count=\${#${config_name}[@]}" + eval "service_name=\"\${${config_name}[0]:-}\"" + eval "download_url=\"\${${config_name}[1]:-}\"" + eval "start_command=\"\${${config_name}[2]:-}\"" + + [[ "$config_count" -ge 4 ]] || fail "Invalid $config_name config. Expected service name, URL and commands" + [[ -n "$service_name" ]] || fail "Invalid $config_name config: service name is empty" + [[ -n "$download_url" ]] || fail "Invalid $config_name config: download URL is empty" + [[ -n "$start_command" ]] || fail "Invalid $config_name config: start command is empty" +} + +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" +} + +validate_environment_line() { + local env_line="$1" + local env_key="${env_line%%=*}" + + [[ "$env_line" == *=* ]] || fail "Invalid environment line: $env_line" + [[ "$env_key" =~ ^[a-zA-Z_][a-zA-Z0-9_]*$ ]] || fail "Invalid environment key: $env_key" +} + +service_env_lines() { + local service_name="$1" + local env_array_name="" + local env_count + local index + local env_line + + case "$service_name" in + iot) + env_array_name="IOT_ENV" + ;; + *) + return 0 + ;; + esac + + eval "env_count=\${#${env_array_name}[@]}" + for ((index=0; index.service`。 - 将服务环境变量写入对应 `.service` 文件。 - 执行 `systemctl daemon-reload`。 -- 设置服务开机启动并启动服务。 +- 设置服务开机启动。 + +## 入口规则 + +只安装 iot: + +```bash +curl -fsSL https://punchsky.cn/install_iot.sh | bash +``` + +只安装 efka: + +```bash +curl -fsSL https://punchsky.cn/install_efka.sh | bash +``` + +本地执行: + +```bash +sudo ./install_iot.sh +sudo ./install_efka.sh +``` ## 顶部配置规则 -脚本顶部是主要维护区域。后续修改下载包、启动命令、环境变量、目录变量时,应优先修改顶部配置。 +服务配置只维护在对应拆分脚本顶部。 ### INSTALL_IOT / INSTALL_EFKA -`INSTALL_IOT` 和 `INSTALL_EFKA` 分别配置服务安装信息,不再合并成统一的 `INSTALL_ITEMS`。 +`install_iot.sh` 维护 `INSTALL_IOT`,`install_efka.sh` 维护 `INSTALL_EFKA`。 格式: @@ -56,40 +80,16 @@ INSTALL_=( `IOT_ENV` 和 `EFKA_ENV` 配置对应项目的生产环境变量,来源参考各项目 `env.file` 中的 `[prod]` 配置。 -格式: - -```bash -IOT_ENV=( - "KEY=value" -) - -EFKA_ENV=( - "KEY=value" -) -``` - -这些变量不会写入 `/etc/default/*`,也不会使用 `EnvironmentFile=`。 - -脚本会直接将它们写入对应 systemd service: +这些变量不会写入 `/etc/default/*`,也不会使用 `EnvironmentFile=`。脚本会直接将它们写入对应 systemd service: ```ini Environment="KEY=value" ``` -这样环境变量由 systemd unit 统一管理,可通过 `systemctl cat ` 查看最终配置。 - ### IOT_DIR_ENV_KEYS / EFKA_DIR_ENV_KEYS `IOT_DIR_ENV_KEYS` 和 `EFKA_DIR_ENV_KEYS` 声明哪些环境变量的值是目录。 -格式: - -```bash -IOT_DIR_ENV_KEYS=( - "ENDPOINT_ROOT_DIR" -) -``` - 安装时脚本会: - 从对应 `*_ENV` 中读取变量值。 @@ -112,15 +112,15 @@ IOT_DIR_ENV_KEYS=( ## 安装流程 -脚本执行顺序: +单个服务脚本执行顺序: 1. 解析命令行参数。 2. 校验 `WORK_DIR` 和服务配置。 3. 检查依赖命令:`wget`、`tar`、`systemctl`。 4. 创建 `WORK_DIR`。 -5. 依次安装 `iot` 和 `efka`。 +5. 安装当前服务。 6. 重新加载 systemd。 -7. enable 并 start 已安装服务。 +7. enable 已安装服务。 单个服务安装流程: @@ -133,7 +133,7 @@ IOT_DIR_ENV_KEYS=( 7. 设置安装目录 owner。 8. 根据 `*_DIR_ENV_KEYS` 创建数据目录。 9. 生成 systemd service 文件。 -10. 记录服务名,后续统一 enable/start。 +10. 记录服务名,后续统一 enable。 ## systemd service 生成规则 @@ -160,12 +160,6 @@ service 文件包含: 只有明确列入 `IOT_DIR_ENV_KEYS` 或 `EFKA_DIR_ENV_KEYS` 的环境变量才会作为目录创建。 -这样做的原因: - -- 避免误把 URL、token、host 等普通变量当成路径。 -- 新增目录型变量时行为明确。 -- 目录创建逻辑和环境变量配置保持关联。 - 新增目录型环境变量时,需要同时修改两处: ```bash @@ -199,7 +193,8 @@ SERVICE_GROUP="${SERVICE_GROUP:-$(id -gn "$SERVICE_USER")}" 可通过参数覆盖: ```bash -./install_service.sh --user app --group app +./install_iot.sh --user app --group app +./install_efka.sh --user app --group app ``` ## 占位符原则 @@ -217,19 +212,13 @@ SERVICE_GROUP="${SERVICE_GROUP:-$(id -gn "$SERVICE_USER")}" - `{work_dir}` - `{service_name}` -示例: - -```bash -"LOG_DIR={work_dir}/var/log/{service_name}" -``` - 如果该变量是目录,需要加入对应 `*_DIR_ENV_KEYS`。 ## 修改和扩展原则 ### 修改环境变量 -只修改顶部对应数组: +只修改对应服务脚本顶部数组: ```bash IOT_ENV=( @@ -237,7 +226,7 @@ IOT_ENV=( ) ``` -修改后重新执行安装脚本,会重新生成 service 文件。 +修改后重新执行对应服务脚本,会重新生成 service 文件。 ### 新增目录 @@ -248,7 +237,7 @@ IOT_ENV=( ### 修改下载包 -只修改: +只修改对应服务脚本: ```bash INSTALL_IOT=( @@ -265,15 +254,15 @@ INSTALL_IOT=( ### 新增服务 -新增服务时应保持当前结构: +新增服务时应按现有拆分结构创建新的 `install_.sh`: 1. 新增 `INSTALL_`。 2. 新增 `_ENV`。 3. 新增 `_DIR_ENV_KEYS`。 -4. 在 `validate_args` 中增加配置校验。 +4. 在 `validate_args` 中校验对应配置。 5. 在 `service_env_lines` 中映射服务名到 env 数组。 6. 在 `service_dir_env_keys` 中映射服务名到目录 key 数组。 -7. 在 `install_programs` 中调用 `install_program_from_config`。 +7. 在 `install_programs` 中调用对应配置。 ## 兼容性原则 @@ -286,13 +275,15 @@ INSTALL_IOT=( 修改脚本后至少执行: ```bash -bash -n shell/install_service.sh +bash -n shell/install_iot.sh +bash -n shell/install_efka.sh ``` 建议额外验证 service 渲染结果: ```bash -bash -c "$(sed '$d' shell/install_service.sh); render_service_file iot app app /opt/app/iot-0.1.0 '/opt/app/iot-0.1.0/bin/iot foreground' '/opt/app/iot-0.1.0/bin/iot stop'" +bash -c "$(sed '$d' shell/install_iot.sh); render_service_file iot app app /opt/app/iot-0.1.0 '/opt/app/iot-0.1.0/bin/iot foreground' '/opt/app/iot-0.1.0/bin/iot stop'" +bash -c "$(sed '$d' shell/install_efka.sh); render_service_file efka app app /opt/app/efka-0.1.0 '/opt/app/efka-0.1.0/bin/efka foreground' '/opt/app/efka-0.1.0/bin/efka stop'" ``` 如果目标机器有 `systemd-analyze`,可进一步验证生成后的 unit 文件: diff --git a/uninstall_service.sh b/uninstall_service.sh index 66bc712..ca2af3e 100755 --- a/uninstall_service.sh +++ b/uninstall_service.sh @@ -4,7 +4,7 @@ set -euo pipefail WORK_DIR="${WORK_DIR:-/opt/app}" PURGE_DATA="${PURGE_DATA:-0}" -# Keep this list in sync with install_service.sh. +# 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=(