370 lines
9.5 KiB
Markdown
370 lines
9.5 KiB
Markdown
# 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`。
|
||
- 将服务环境变量写入对应 `.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_<APP>=(
|
||
"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/<service_name>`。
|
||
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_name>.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_<app>.sh`:
|
||
|
||
1. 新增 `INSTALL_<APP>`。
|
||
2. 新增 `<APP>_ENV`。
|
||
3. 新增 `<APP>_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` 的思路同步调整。
|