# install service 脚本规则与参考原则 本文档说明 `shell/install_iot.sh`、`shell/install_efka.sh` 的配置规则、安装流程和后续维护原则。 ## 目标 安装逻辑已经按服务拆分: - `install_iot.sh`: 只安装并配置 `iot` 服务。 - `install_efka.sh`: 只安装并配置 `efka` 服务。 服务脚本负责: - 下载服务发布包。 - 解压到 `WORK_DIR`。 - 删除下载到 `WORK_DIR` 的压缩包。 - 创建运行所需目录。 - 生成 `/etc/systemd/system/.service`。 - 将服务环境变量写入对应 `.service` 文件。 - 执行 `systemctl daemon-reload`。 - 设置服务开机启动。 - `install_iot.sh` 会将 `{install_dir}/bin/iot_ctrl` 软链接到 `/usr/local/bin/iot_ctrl`。 ## 入口规则 只安装 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.sh` 维护 `INSTALL_IOT`,`install_efka.sh` 维护 `INSTALL_EFKA`。 格式: ```bash INSTALL_=( "service_name" "download_url" "start_command" "stop_command" "service_user" "service_group" ) ``` 字段说明: - `service_name`: systemd 服务名,例如 `iot`、`efka`。 - `download_url`: 发布包下载地址,仅支持 `.tar.gz` 或 `.tgz`。 - `start_command`: systemd `ExecStart` 命令。 - `stop_command`: systemd `ExecStop` 命令,可为空。 - `service_user`: 服务运行用户,可为空;为空时使用脚本默认 `SERVICE_USER`。 - `service_group`: 服务运行组,可为空;为空时使用脚本默认 `SERVICE_GROUP`。 命令中可使用占位符: - `{install_dir}`: 解压后的服务安装目录,当前为 `{work_dir}/{service_name}`,例如 `/opt/app/iot`、`/opt/app/efka`。 - `{work_dir}`: 脚本工作目录。 - `{service_name}`: 当前服务名。 ### IOT_ENV / EFKA_ENV `IOT_ENV` 和 `EFKA_ENV` 配置对应项目的生产环境变量,来源参考各项目 `env.file` 中的 `[prod]` 配置。 这些变量不会写入 `/etc/default/*`,也不会使用 `EnvironmentFile=`。脚本会直接将它们写入对应 systemd service: ```ini Environment="KEY=value" ``` ### IOT_DIR_ENV_KEYS / EFKA_DIR_ENV_KEYS `IOT_DIR_ENV_KEYS` 和 `EFKA_DIR_ENV_KEYS` 声明哪些环境变量的值是目录。 安装时脚本会: - 从对应 `*_ENV` 中读取变量值。 - 替换占位符。 - 要求目录必须是绝对路径。 - 如果目录不存在,则创建。 - 将目录 owner 设置为服务用户和服务组。 ### IOT_PARENT_DIR_ENV_KEYS `IOT_PARENT_DIR_ENV_KEYS` 声明哪些环境变量的值是文件路径,脚本会创建这些文件路径的父目录。 例如 `IOT_CTRL_SOCKET_PATH=/var/lib/iot/ctl.sock` 是 Unix socket 文件路径,脚本应创建并授权 `/var/lib/iot`,不能把 `/var/lib/iot/ctl.sock` 当目录创建。 当前 iot 会创建: - `/var/lib/endpoint/database/` - `/var/lib/endpoint/endpoint_log` - `/var/lib/iot/mnesia/` - `/var/lib/iot`,来自 `IOT_CTRL_SOCKET_PATH` 的父目录 当前 efka 会创建: - `/var/lib/efka/dets/` - `/var/lib/efka/docker/` - `/var/lib/efka/mnesia` ## 安装流程 单个服务脚本执行顺序: 1. 解析命令行参数。 2. 校验 `WORK_DIR` 和服务配置。 3. 检查依赖命令:`wget`、`dirname`、`ln`、`tar`、`systemctl`。 4. 创建 `WORK_DIR`。 5. 安装当前服务。 6. 重新加载 systemd。 7. enable 已安装服务。 单个服务安装流程: 1. 校验服务名、下载地址、启动命令。 2. 根据服务名推导安装目录,默认是 `/opt/app/`。 3. 下载压缩包到临时文件。 4. 保存压缩包到 `WORK_DIR`。 5. 创建安装目录。 6. 解压压缩包。 7. 删除保存到 `WORK_DIR` 的压缩包。 8. 设置安装目录 owner。 9. 根据 `*_DIR_ENV_KEYS` 创建数据目录,并根据 `*_PARENT_DIR_ENV_KEYS` 创建文件路径父目录。 10. iot 安装时创建 `/usr/local/bin/iot_ctrl` 软链接。 11. 生成 systemd service 文件。 12. 记录服务名,后续统一 enable。 ## iot_ctrl 构建和打包原则 `iot_ctrl` 位于 `iot/go_extend`,应在发布包构建阶段编译,不在 `install_iot.sh` 中编译。 目标机器不应依赖 Go 编译环境。发布包需要提前包含: ```text iot/ bin/ iot iot_ctrl ``` 安装后脚本会检查 `{install_dir}/bin/iot_ctrl` 是否存在且可执行。如果不存在,安装会失败并提示需要在打包前构建该工具。 安装成功后会创建软链接: ```text /usr/local/bin/iot_ctrl -> /opt/app/iot/bin/iot_ctrl ``` 构建示例: ```bash cd iot/go_extend GOOS=linux GOARCH=amd64 CGO_ENABLED=0 make build ``` 如果目标机器是 arm64: ```bash GOOS=linux GOARCH=arm64 CGO_ENABLED=0 make build ``` ## systemd service 生成规则 每个服务会生成: ```text /etc/systemd/system/.service ``` service 文件包含: - `User` - `Group` - `WorkingDirectory` - 多行 `Environment="KEY=value"` - `ExecStart` - `ExecStop` - `Restart=on-failure` - `RestartSec=5` 环境变量必须写入服务自己的 `.service` 文件,而不是外部环境文件。 ## 目录创建原则 只有明确列入 `IOT_DIR_ENV_KEYS` 或 `EFKA_DIR_ENV_KEYS` 的环境变量才会作为目录创建。 新增目录型环境变量时,需要同时修改两处: ```bash IOT_ENV=( "NEW_DATA_DIR=/var/lib/iot/new_data" ) IOT_DIR_ENV_KEYS=( "NEW_DATA_DIR" ) ``` 非目录型环境变量只需要加入 `*_ENV`,不要加入 `*_DIR_ENV_KEYS`。 如果环境变量是文件路径,需要把变量名加入对应的 `*_PARENT_DIR_ENV_KEYS`,脚本只创建父目录: ```bash IOT_ENV=( "IOT_CTRL_SOCKET_PATH=/var/lib/iot/ctl.sock" ) IOT_PARENT_DIR_ENV_KEYS=( "IOT_CTRL_SOCKET_PATH" ) ``` ## 权限原则 脚本支持 root 或普通用户运行。 - 如果当前是 root,直接执行需要 root 权限的命令。 - 如果不是 root,通过 `sudo` 执行。 - 数据目录和安装目录 owner 会设置为服务运行用户和组。 - systemd service 文件写入 `/etc/systemd/system`,需要 root 权限。 默认服务用户: ```bash SERVICE_USER="${SERVICE_USER:-$CURRENT_USER}" SERVICE_GROUP="${SERVICE_GROUP:-$(id -gn "$SERVICE_USER")}" ``` 可通过参数覆盖: ```bash ./install_iot.sh --user app --group app ./install_efka.sh --user app --group app ``` ## 占位符原则 以下位置支持占位符: - 启动命令。 - 停止命令。 - 环境变量值。 - 目录路径。 支持的占位符: - `{install_dir}` - `{work_dir}` - `{service_name}` 如果该变量是目录,需要加入对应 `*_DIR_ENV_KEYS`。如果该变量是文件路径,需要加入对应 `*_PARENT_DIR_ENV_KEYS`。 `{install_dir}` 不从压缩包文件名推导,不带版本号,固定使用服务名作为目录名。 ## 修改和扩展原则 ### 修改环境变量 只修改对应服务脚本顶部数组: ```bash IOT_ENV=( "IOT_API_URL=http://127.0.0.1/api/v1" ) ``` 修改后重新执行对应服务脚本,会重新生成 service 文件。 ### 新增目录 同时修改: - `IOT_ENV` 或 `EFKA_ENV` - `IOT_DIR_ENV_KEYS` 或 `EFKA_DIR_ENV_KEYS` ### 新增文件路径 同时修改: - `IOT_ENV` 或 `EFKA_ENV` - 对应的 `*_PARENT_DIR_ENV_KEYS` ### 修改下载包 只修改对应服务脚本: ```bash INSTALL_IOT=( "iot" "https://example.com/iot-x.y.z.tar.gz" ... ) ``` 下载包后缀必须是: - `.tar.gz` - `.tgz` ### 新增服务 新增服务时应按现有拆分结构创建新的 `install_.sh`: 1. 新增 `INSTALL_`。 2. 新增 `_ENV`。 3. 新增 `_DIR_ENV_KEYS`。 4. 在 `validate_args` 中校验对应配置。 5. 在 `service_env_lines` 中映射服务名到 env 数组。 6. 在 `service_dir_env_keys` 中映射服务名到目录 key 数组。 7. 如服务有文件路径型环境变量,在 `service_parent_dir_env_keys` 中映射服务名到父目录 key 数组。 8. 在 `install_programs` 中调用对应配置。 ## 兼容性原则 脚本当前兼容 Bash 3.2,不使用 `local -n` 等较新的 Bash 特性。 读取动态数组时使用 `eval`,因此数组名必须由脚本内部固定传入,不应使用用户输入构造数组名。 ## 验证建议 修改脚本后至少执行: ```bash bash -n shell/install_iot.sh bash -n shell/install_efka.sh ``` 建议额外验证 service 渲染结果: ```bash bash -c "$(sed '$d' shell/install_iot.sh); render_service_file iot app app /opt/app/iot '/opt/app/iot/bin/iot foreground' '/opt/app/iot/bin/iot stop'" bash -c "$(sed '$d' shell/install_efka.sh); render_service_file efka app app /opt/app/efka '/opt/app/efka/bin/efka foreground' '/opt/app/efka/bin/efka stop'" ``` 如果目标机器有 `systemd-analyze`,可进一步验证生成后的 unit 文件: ```bash systemd-analyze verify /etc/systemd/system/iot.service systemd-analyze verify /etc/systemd/system/efka.service ``` ## 注意事项 - 环境变量中如包含空格或特殊字符,需要确认 systemd `Environment=` 语法是否仍然正确。 - token 等敏感信息会写入 systemd service 文件,需控制 `/etc/systemd/system/*.service` 的读取权限和服务器访问权限。 - 数据目录和文件路径型变量必须使用绝对路径。 - iot 发布包必须包含可执行的 `bin/iot_ctrl`,否则不会创建 `/usr/local/bin/iot_ctrl` 链接。 - 现有卸载脚本如果需要同步目录清理规则,应按 `IOT_DIR_ENV_KEYS` / `EFKA_DIR_ENV_KEYS` 的思路同步调整。