This commit is contained in:
anlicheng 2026-06-01 15:01:57 +08:00
parent 16e63a5c74
commit 719b539b3f
4 changed files with 105 additions and 37 deletions

View File

@ -152,6 +152,15 @@ strip_archive_suffix() {
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"
}
parse_args() {
while [[ $# -gt 0 ]]; do
case "$1" in
@ -394,21 +403,17 @@ install_program() {
local stop_command="$4"
local service_user="$5"
local service_group="$6"
local clean_url="$download_url"
local archive_name
validate_install_item "$service_name" "$download_url" "$start_command"
clean_url="${clean_url%%\?*}"
clean_url="${clean_url%%#*}"
archive_name="$(basename "$clean_url")"
archive_name="$(archive_name_from_url "$download_url")"
[[ -n "$archive_name" ]] || fail "Cannot parse archive filename from URL: $download_url"
local app_dir_name
app_dir_name="$(strip_archive_suffix "$archive_name")"
strip_archive_suffix "$archive_name" >/dev/null
local archive_path="${WORK_DIR}/${archive_name}"
local install_dir="${WORK_DIR}/${app_dir_name}"
local install_dir="${WORK_DIR}/${service_name}"
local service_file="/etc/systemd/system/${service_name}.service"
local temp_archive
temp_archive="$(mktemp)"
@ -429,6 +434,9 @@ install_program() {
log "Extracting archive to $install_dir"
run_as_root tar -zxvf "$archive_path" -C "$install_dir"
log "Removing downloaded archive: $archive_path"
run_as_root rm -f "$archive_path"
log "Setting owner for install directory: ${service_user}:${service_group}"
run_as_root chown -R "${service_user}:${service_group}" "$install_dir"

View File

@ -158,6 +158,15 @@ strip_archive_suffix() {
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"
}
parse_args() {
while [[ $# -gt 0 ]]; do
case "$1" in
@ -441,6 +450,18 @@ create_parent_dependency_dirs_from_env() {
done < <(service_parent_dir_env_keys "$service_name")
}
link_iot_ctrl() {
local install_dir="$1"
local iot_ctrl_path="${install_dir}/bin/iot_ctrl"
local link_path="/usr/local/bin/iot_ctrl"
[[ -x "$iot_ctrl_path" ]] || fail "Missing executable iot control tool: $iot_ctrl_path. Build it before packaging the release"
log "Linking iot control tool: $link_path -> $iot_ctrl_path"
run_as_root install -d -m 0755 "$(dirname "$link_path")"
run_as_root ln -sfn "$iot_ctrl_path" "$link_path"
}
install_program() {
local service_name="$1"
local download_url="$2"
@ -448,21 +469,17 @@ install_program() {
local stop_command="$4"
local service_user="$5"
local service_group="$6"
local clean_url="$download_url"
local archive_name
validate_install_item "$service_name" "$download_url" "$start_command"
clean_url="${clean_url%%\?*}"
clean_url="${clean_url%%#*}"
archive_name="$(basename "$clean_url")"
archive_name="$(archive_name_from_url "$download_url")"
[[ -n "$archive_name" ]] || fail "Cannot parse archive filename from URL: $download_url"
local app_dir_name
app_dir_name="$(strip_archive_suffix "$archive_name")"
strip_archive_suffix "$archive_name" >/dev/null
local archive_path="${WORK_DIR}/${archive_name}"
local install_dir="${WORK_DIR}/${app_dir_name}"
local install_dir="${WORK_DIR}/${service_name}"
local service_file="/etc/systemd/system/${service_name}.service"
local temp_archive
temp_archive="$(mktemp)"
@ -483,11 +500,15 @@ install_program() {
log "Extracting archive to $install_dir"
run_as_root tar -zxvf "$archive_path" -C "$install_dir"
log "Removing downloaded archive: $archive_path"
run_as_root rm -f "$archive_path"
log "Setting owner for install directory: ${service_user}:${service_group}"
run_as_root chown -R "${service_user}:${service_group}" "$install_dir"
create_dependency_dirs_from_env "$service_name" "$install_dir" "$service_user" "$service_group"
create_parent_dependency_dirs_from_env "$service_name" "$install_dir" "$service_user" "$service_group"
link_iot_ctrl "$install_dir"
write_service_file "$service_file" "$service_name" "$service_user" "$service_group" "$install_dir" "$start_command" "$stop_command"
INSTALLED_SERVICES+=("$service_name")
@ -549,6 +570,7 @@ main() {
require_command wget
require_root_runner
require_command dirname
require_command ln
require_command tar
require_command systemctl

View File

@ -13,11 +13,13 @@
- 下载服务发布包。
- 解压到 `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`
## 入口规则
@ -72,7 +74,7 @@ INSTALL_<APP>=(
命令中可使用占位符:
- `{install_dir}`: 解压后的服务安装目录。
- `{install_dir}`: 解压后的服务安装目录,当前为 `{work_dir}/{service_name}`,例如 `/opt/app/iot``/opt/app/efka`
- `{work_dir}`: 脚本工作目录。
- `{service_name}`: 当前服务名。
@ -123,7 +125,7 @@ Environment="KEY=value"
1. 解析命令行参数。
2. 校验 `WORK_DIR` 和服务配置。
3. 检查依赖命令:`wget``dirname``tar`、`systemctl`
3. 检查依赖命令:`wget``dirname``ln`、`tar`、`systemctl`
4. 创建 `WORK_DIR`
5. 安装当前服务。
6. 重新加载 systemd。
@ -132,15 +134,51 @@ Environment="KEY=value"
单个服务安装流程:
1. 校验服务名、下载地址、启动命令。
2. 根据下载地址推导压缩包名和安装目录
2. 根据服务名推导安装目录,默认是 `/opt/app/<service_name>`
3. 下载压缩包到临时文件。
4. 保存压缩包到 `WORK_DIR`
5. 创建安装目录。
6. 解压压缩包。
7. 设置安装目录 owner。
8. 根据 `*_DIR_ENV_KEYS` 创建数据目录,并根据 `*_PARENT_DIR_ENV_KEYS` 创建文件路径父目录。
9. 生成 systemd service 文件。
10. 记录服务名,后续统一 enable。
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 生成规则
@ -233,6 +271,8 @@ SERVICE_GROUP="${SERVICE_GROUP:-$(id -gn "$SERVICE_USER")}"
如果该变量是目录,需要加入对应 `*_DIR_ENV_KEYS`。如果该变量是文件路径,需要加入对应 `*_PARENT_DIR_ENV_KEYS`
`{install_dir}` 不从压缩包文件名推导,不带版本号,固定使用服务名作为目录名。
## 修改和扩展原则
### 修改环境变量
@ -309,8 +349,8 @@ 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-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'"
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 文件:
@ -325,4 +365,5 @@ 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` 的思路同步调整。

View File

@ -27,7 +27,8 @@ Default behavior:
- Disable services from boot.
- Remove /etc/systemd/system/<service>.service and service backups.
- Run systemctl daemon-reload and reset-failed.
- Remove downloaded archives and extracted program directories under WORK_DIR.
- Remove downloaded archives and service-name program directories under WORK_DIR.
- Remove /usr/local/bin/iot_ctrl.
- Keep dependency/data directories unless --purge-data is passed.
EOF
}
@ -100,16 +101,6 @@ validate_args() {
[[ "$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"
@ -191,7 +182,6 @@ remove_program_files() {
local service_user
local service_group
local archive_name
local app_dir_name
local archive_path
local install_dir
@ -205,9 +195,8 @@ remove_program_files() {
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}"
install_dir="${WORK_DIR}/${service_name}"
log "Removing downloaded archive: $archive_path"
run_as_root rm -f "$archive_path"
@ -223,6 +212,13 @@ remove_program_files() {
set -u
}
remove_global_links() {
local link_path="/usr/local/bin/iot_ctrl"
log "Removing global command link: $link_path"
run_as_root rm -f "$link_path"
}
remove_dependency_dirs() {
local dependency_dirs="$1"
local install_dir="$2"
@ -263,6 +259,7 @@ main() {
stop_and_disable_services
remove_service_files
reload_systemd
remove_global_links
remove_program_files
log "Uninstall completed"