diff --git a/install_service.md b/install_service.md new file mode 100644 index 0000000..e7b3a85 --- /dev/null +++ b/install_service.md @@ -0,0 +1,310 @@ +# install_service.sh 规则与参考原则 + +本文档说明 `shell/install_service.sh` 的配置规则、安装流程和后续维护原则。 + +## 目标 + +`install_service.sh` 用于自动安装并通过 systemd 管理 `iot` 和 `efka` 服务。 + +脚本负责: + +- 下载服务发布包。 +- 解压到 `WORK_DIR`。 +- 创建运行所需目录。 +- 生成 `/etc/systemd/system/.service`。 +- 将服务环境变量写入对应 `.service` 文件。 +- 执行 `systemctl daemon-reload`。 +- 设置服务开机启动并启动服务。 + +## 顶部配置规则 + +脚本顶部是主要维护区域。后续修改下载包、启动命令、环境变量、目录变量时,应优先修改顶部配置。 + +### INSTALL_IOT / INSTALL_EFKA + +`INSTALL_IOT` 和 `INSTALL_EFKA` 分别配置服务安装信息,不再合并成统一的 `INSTALL_ITEMS`。 + +格式: + +```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}`: 当前服务名。 + +### IOT_ENV / EFKA_ENV + +`IOT_ENV` 和 `EFKA_ENV` 配置对应项目的生产环境变量,来源参考各项目 `env.file` 中的 `[prod]` 配置。 + +格式: + +```bash +IOT_ENV=( + "KEY=value" +) + +EFKA_ENV=( + "KEY=value" +) +``` + +这些变量不会写入 `/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` 中读取变量值。 +- 替换占位符。 +- 要求目录必须是绝对路径。 +- 如果目录不存在,则创建。 +- 将目录 owner 设置为服务用户和服务组。 + +当前 iot 会创建: + +- `/var/lib/endpoint/database/` +- `/var/lib/endpoint/endpoint_log` +- `/var/lib/iot/mnesia/` + +当前 efka 会创建: + +- `/var/lib/efka/dets/` +- `/var/lib/efka/docker/` +- `/var/lib/efka/mnesia` + +## 安装流程 + +脚本执行顺序: + +1. 解析命令行参数。 +2. 校验 `WORK_DIR` 和服务配置。 +3. 检查依赖命令:`wget`、`tar`、`systemctl`。 +4. 创建 `WORK_DIR`。 +5. 依次安装 `iot` 和 `efka`。 +6. 重新加载 systemd。 +7. enable 并 start 已安装服务。 + +单个服务安装流程: + +1. 校验服务名、下载地址、启动命令。 +2. 根据下载地址推导压缩包名和安装目录。 +3. 下载压缩包到临时文件。 +4. 保存压缩包到 `WORK_DIR`。 +5. 创建安装目录。 +6. 解压压缩包。 +7. 设置安装目录 owner。 +8. 根据 `*_DIR_ENV_KEYS` 创建数据目录。 +9. 生成 systemd service 文件。 +10. 记录服务名,后续统一 enable/start。 + +## 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` 的环境变量才会作为目录创建。 + +这样做的原因: + +- 避免误把 URL、token、host 等普通变量当成路径。 +- 新增目录型变量时行为明确。 +- 目录创建逻辑和环境变量配置保持关联。 + +新增目录型环境变量时,需要同时修改两处: + +```bash +IOT_ENV=( + "NEW_DATA_DIR=/var/lib/iot/new_data" +) + +IOT_DIR_ENV_KEYS=( + "NEW_DATA_DIR" +) +``` + +非目录型环境变量只需要加入 `*_ENV`,不要加入 `*_DIR_ENV_KEYS`。 + +## 权限原则 + +脚本支持 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_service.sh --user app --group app +``` + +## 占位符原则 + +以下位置支持占位符: + +- 启动命令。 +- 停止命令。 +- 环境变量值。 +- 目录路径。 + +支持的占位符: + +- `{install_dir}` +- `{work_dir}` +- `{service_name}` + +示例: + +```bash +"LOG_DIR={work_dir}/var/log/{service_name}" +``` + +如果该变量是目录,需要加入对应 `*_DIR_ENV_KEYS`。 + +## 修改和扩展原则 + +### 修改环境变量 + +只修改顶部对应数组: + +```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` + +### 修改下载包 + +只修改: + +```bash +INSTALL_IOT=( + "iot" + "https://example.com/iot-x.y.z.tar.gz" + ... +) +``` + +下载包后缀必须是: + +- `.tar.gz` +- `.tgz` + +### 新增服务 + +新增服务时应保持当前结构: + +1. 新增 `INSTALL_`。 +2. 新增 `_ENV`。 +3. 新增 `_DIR_ENV_KEYS`。 +4. 在 `validate_args` 中增加配置校验。 +5. 在 `service_env_lines` 中映射服务名到 env 数组。 +6. 在 `service_dir_env_keys` 中映射服务名到目录 key 数组。 +7. 在 `install_programs` 中调用 `install_program_from_config`。 + +## 兼容性原则 + +脚本当前兼容 Bash 3.2,不使用 `local -n` 等较新的 Bash 特性。 + +读取动态数组时使用 `eval`,因此数组名必须由脚本内部固定传入,不应使用用户输入构造数组名。 + +## 验证建议 + +修改脚本后至少执行: + +```bash +bash -n shell/install_service.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'" +``` + +如果目标机器有 `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_DIR_ENV_KEYS` / `EFKA_DIR_ENV_KEYS` 的思路同步调整。