diff --git a/AGENTS.md b/AGENTS.md index 4bab7bf..cf17c5c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -62,7 +62,27 @@ amber-pm-convert --base amber-pm-trixie /path/to/package.deb amber-pm-convert --base amber-pm-bookworm-spark-wine /path/to/package.deb --pkgname new-pkg --version 1.0.0 ``` -### 2.2 amber-pm-dstore-patch +### 2.2 amber-pm-addons-maker + +**功能**:创建 APM addons 包,用于在 base 之上叠加额外的环境层。 + +**使用场景**: +- 为 base 环境添加 NVIDIA 驱动、Mesa 补丁 +- 为 base 环境添加 Git、Java、Python 等运行时 +- 所有基于该 base 的应用自动继承 addons 环境 + +**示例**: +```bash +amber-pm-addons-maker --base amber-pm-bookworm --manual --pkgname amber-pm-bookworm-nvidia-addons +amber-pm-addons-maker --base amber-pm-trixie /path/to/mesa-patch.deb --pkgname amber-pm-trixie-mesa-addons +``` + +**说明**: +- addons 包命名格式建议为 `-<描述>-addons` +- 安装后自动在对应 base 的 `info_layer_addons.d/` 中注册 +- 支持 `--manual` 参数进入交互式创建流程 + +### 2.3 amber-pm-dstore-patch **功能**:修补应用商店相关配置。 diff --git a/Packaging-demo/README.md b/Packaging-demo/README.md index 63bd45f..f82386c 100644 --- a/Packaging-demo/README.md +++ b/Packaging-demo/README.md @@ -30,7 +30,10 @@ APM 使用 OverlayFS 来管理软件包的文件系统层级,从上到下的 3. **依赖层** 由 `info` 文件递归解析出的所有依赖包 -4. **底层 Runtime** +4. **Addons 层** + 由 `info_layer_addons` 和 `info_layer_addons.d` 注册的 addons 包,位于对应 base 之上 + +5. **底层 Runtime** 最基础的运行时环境(如 `amber-pm-bookworm`) 这种层叠结构允许上层文件覆盖下层文件,实现灵活、高效的依赖管理与环境定制。 @@ -230,6 +233,86 @@ amber-pm-bookworm-mesa:amber-pm-bookworm --- +## info_layer_addons / info_layer_addons.d(Addons 层) + +`info_layer_addons` 和 `info_layer_addons.d` 是 **1.3.0+** 引入的标准,用于在 **base 之上叠加 addons 层**,使所有运行在该 base 上的应用自动继承 addons 环境。 + +### 使用场景 + +- 为所有基于同一 base 的应用统一注入 NVIDIA 驱动 +- 为所有基于同一 base 的应用统一更新 Mesa / Vulkan +- 为所有基于同一 base 的应用统一提供 Git、Java、Python 等运行时环境 +- 无需修改 base 本身,即可同步变更环境 + +### 规则说明 + +- `info_layer_addons` — 位于 base 包目录下的可选文件,每行一个 addons 包名 +- `info_layer_addons.d/` — 位于 base 包目录下的可选目录,包含文件名格式为 `优先级-addons包名` 的文件 +- 数字越小优先级越高(排序靠前) +- `.d` 目录中的 addons 优先级高于 `info_layer_addons` 文件中的 addons +- **即使 base 没有 `info` 文件,也可以有 `info_layer_addons`**(最底层 base 也可以有 addons) +- APM 在运行时自动读取并挂载这些 addons + +### Addons 包结构 + +Addons 包是一种特殊的 APM 包,**不需要 `info` 文件和 `entries/` 目录**: + +``` +/var/lib/apm/-<描述>-addons/ +├── files +│ ├── core/ # upperdir(addons 的文件内容) +│ └── work/ # OverlayFS 工作目录 +``` + +### Addons 包命名规范 + +建议格式:`-<描述>-addons` + +示例: +- `amber-pm-bookworm-nvidia-addons` +- `amber-pm-trixie-mesa-addons` +- `amber-pm-bookworm-java-addons` + +### 创建 Addons 包 + +推荐使用 `amber-pm-addons-maker` 工具: + +```bash +# 手动模式(交互式 shell 安装软件后打包) +amber-pm-addons-maker --base amber-pm-bookworm --manual --pkgname amber-pm-bookworm-nvidia-addons + +# 自动模式(直接安装 deb 后打包) +amber-pm-addons-maker --base amber-pm-bookworm /path/to/nvidia-driver.deb --pkgname amber-pm-bookworm-nvidia-addons +``` + +安装 addons 包后,它会在对应 base 的 `info_layer_addons.d/` 目录中自动注册,所有依赖该 base 的应用下次启动时即可自动加载该 addons。 + +### 示例 + +假设 `amber-pm-bookworm-nvidia-addons` 已安装并注册到 `amber-pm-bookworm`: + +`amber-pm-bookworm/info_layer_addons.d/50-amber-pm-bookworm-nvidia-addons`: + +``` +amber-pm-bookworm-nvidia-addons +``` + +应用包 `eom` 的 `info`: + +``` +amber-pm-bookworm +``` + +最终 lowerdir 顺序: + +``` +amber-pm-bookworm-nvidia-addons:amber-pm-bookworm +``` + +所有运行 `apm run eom` 的实例都会自动加载 NVIDIA addons。 + +--- + ## info_env(环境变量层 · 高级功能) `info_env` 是一个 **可选的高级特性**,用于为 APM 容器运行时提供**分层的环境变量配置能力**。 diff --git a/README.md b/README.md index bffde5e..6916cb4 100644 --- a/README.md +++ b/README.md @@ -65,22 +65,41 @@ Commands: ## APM Deb 包全自动转换器使用方法 ``` -用法: amber-pm-convert --base [--base ...] [--pkgname <包名>] [--version <版本号>] +用法: amber-pm-convert --base [--base ...] [--addons ...] [--pkgname <包名>] [--version <版本号>] 参数说明: --basename 必填参数,指定基础环境名称,可多次使用指定多个基础环境 + --addons 可选参数,指定额外挂载的 addons 包,可多次使用 deb文件路径 必填参数,要转换的 Deb 文件路径 --pkgname 可选参数,指定新包的包名(默认使用原 Deb 包名) --version 可选参数,指定新包的版本号(默认在原版本后追加'-apm') 示例: amber-pm-convert --base amber-pm-trixie /path/to/package.deb + amber-pm-convert --base amber-pm-trixie --addons amber-pm-trixie-nvidia-addons /path/to/package.deb amber-pm-convert --base amber-pm-bookworm-spark-wine /path/to/package.deb --pkgname new-pkg --version 1.0.0 最下层的 base 在最后,从上到下写 base ``` +## APM Addons 包创建工具 + +``` +用法: amber-pm-addons-maker --base [--manual] [--pkgname <包名>] [--version <版本>] [deb文件路径] + +参数说明: + --base 必填参数,指定基础环境名称 + --manual 启用手动模式:融合挂载后打开交互 shell + --pkgname 可选参数,指定包名(建议格式:-<描述>-addons) + --version 可选参数,指定版本号(默认 1.0.0-apm) + deb文件路径 可选参数,要安装到 addons 环境中的 Deb 文件 + +示例: + amber-pm-addons-maker --base amber-pm-bookworm --manual --pkgname amber-pm-bookworm-nvidia-addons + amber-pm-addons-maker --base amber-pm-trixie /path/to/mesa.deb --pkgname amber-pm-trixie-mesa-addons +``` + > 注意:APM 软件包为特殊的 Deb 软件包,因此若您在使用 Debian 或其他使用 dpkg 管理软件包的发行版,也可使用 apt 直接将 APM 软件包安装至系统中,同样可供使用。对于此种情况,请使用系统自带的 apt 进行软件包管理。 ## APM 的原理和软件包的介绍 @@ -88,6 +107,8 @@ Commands: 详见 [Packaging-demo](Packaging-demo)。 > 1.1.5+ 版本支持了覆盖 base 功能,相见 https://gitee.com/amber-ce/amber-pm/blob/master/Packaging-demo/README.md#info_layer_override-%E6%96%87%E4%BB%B6 +> +> 1.3.0+ 版本支持了 addons 层功能,相见 https://gitee.com/amber-ce/amber-pm/blob/master/Packaging-demo/README.md#info_layer_addons--info_layer_addonsdaddons-%E5%B1%82 ## APM 构建 Tips diff --git a/build.config b/build.config index 7105501..f33e59e 100644 --- a/build.config +++ b/build.config @@ -1 +1 @@ -@VERSION@=1.2.5 +@VERSION@=1.3.0 diff --git a/src/usr/bin/amber-pm-addons-maker b/src/usr/bin/amber-pm-addons-maker new file mode 100755 index 0000000..7a78d37 --- /dev/null +++ b/src/usr/bin/amber-pm-addons-maker @@ -0,0 +1,401 @@ +#!/bin/bash + +# APM Addons 包创建工具 - 用于创建可在 base 上叠加的 addons 层 +log.warn() { echo -e "[\e[33mWARN\e[0m]: \e[1m$*\e[0m"; } +log.error() { echo -e "[\e[31mERROR\e[0m]: \e[1m$*\e[0m"; } +log.info() { echo -e "[\e[96mINFO\e[0m]: \e[1m$*\e[0m"; } +log.debug() { echo -e "[\e[32mDEBUG\e[0m]: \e[1m$*\e[0m"; } + +SCRIPT_NAME=$(basename "$0") + +if ! command -v dpkg > /dev/null ; then + log.error "若想使用APM addons包创建工具,您需先安装dpkg" + exit 1 +fi + +# 显示用法信息 +usage() { + echo "用法: $SCRIPT_NAME --base [--manual] [--pkgname <包名>] [--version <版本>] [deb文件路径]" + echo "" + echo "参数说明:" + echo " --base 必填参数,指定基础环境名称(addons 将叠加在此 base 上)" + echo " --manual 启用手动模式:融合挂载后打开交互 shell,退出后脚本继续" + echo " --pkgname 可选参数,指定新包的包名(默认格式:-addons)" + echo " --version 可选参数,指定新包的版本号(默认:1.0.0-apm)" + echo " deb文件路径 可选参数,要安装到 addons 环境中的 Deb 文件路径" + echo "" + echo "示例:" + echo " $SCRIPT_NAME --base amber-pm-bookworm" + echo " $SCRIPT_NAME --base amber-pm-bookworm --manual" + echo " $SCRIPT_NAME --base amber-pm-trixie --pkgname amber-pm-trixie-nvidia-addons --version 1.0.0" + echo " $SCRIPT_NAME --base amber-pm-bookworm /path/to/nvidia-driver.deb" + echo "" + echo "说明: addons 包命名格式建议为 -<描述>-addons" +} + +# 解析参数 +BASE_NAME="" +DEB_PATH="" +PKGNAME="" +VERSION="" +MANUAL_MODE=false + +while [ $# -gt 0 ]; do + case "$1" in + --base) + if [ -z "$2" ]; then + log.error "--base 后需要跟名称" + usage + exit 1 + fi + BASE_NAME="$2" + shift 2 + ;; + --pkgname) + PKGNAME="$2" + shift 2 + ;; + --version) + VERSION="$2" + shift 2 + ;; + --manual) + MANUAL_MODE=true + shift + ;; + -*) + log.error "未知选项: $1" + usage + exit 1 + ;; + *) + if [ -z "$DEB_PATH" ]; then + DEB_PATH="$1" + shift + else + log.error "未知参数或多余的参数: $1" + usage + exit 1 + fi + ;; + esac +done + +# 基本参数验证 +if [ -z "$BASE_NAME" ]; then + log.error "错误:必须提供 --base 参数" + usage + exit 1 +fi + +# 检查 base 是否存在 +BASE_DIR="/var/lib/apm/${BASE_NAME}" +if [ ! -d "$BASE_DIR" ]; then + # 也检查 ace-env 内的路径 + BASE_DIR_ALT="/var/lib/apm/apm/files/ace-env/var/lib/apm/${BASE_NAME}" + if [ -d "$BASE_DIR_ALT" ]; then + BASE_DIR="$BASE_DIR_ALT" + else + log.error "错误:基础环境不存在: $BASE_NAME" + exit 1 + fi +fi + +log.info "基础环境: $BASE_NAME" +log.info "基础环境路径: $BASE_DIR" + +# 如果传入了 DEB_PATH,检查文件是否存在 +if [ -n "$DEB_PATH" ] && [ ! -f "$DEB_PATH" ]; then + log.error "错误:DEB文件不存在: $DEB_PATH" + exit 1 +fi + +# 1. 创建临时工作目录 +CRAFT_DIR="$HOME/apm-addons-craft-$$" +log.info "创建临时工作目录: $CRAFT_DIR" +mkdir -p "$CRAFT_DIR"/{core,work,mergedir,new-pkg} +export CRAFT_DIR + +# 检查是否已挂载,避免重复挂载 +cleanup_mount() { + if mountpoint -q "$CRAFT_DIR/mergedir"; then + log.info "解除挂载: $CRAFT_DIR/mergedir" + sudo umount "$CRAFT_DIR/mergedir" || true + fi +} + +# 清理函数 +cleanup() { + log.info "开始清理..." + cleanup_mount + if [ -d "$CRAFT_DIR" ]; then + log.info "删除临时目录: $CRAFT_DIR" + sudo rm -rf "$CRAFT_DIR" + fi +} + +# 设置退出时清理 +trap cleanup EXIT + +# 2. 构建 lowerdir 路径(base + base 的现有 addons) +log.info "构建 overlay lowerdir 路径..." +LOWERDIRS=() + +# 添加 base 本身 +BASE_CORE_PATH="${BASE_DIR}/files/core" +BASE_ACEENV_PATH="${BASE_DIR}/files/ace-env" + +if [ -d "$BASE_ACEENV_PATH" ]; then + log.info "使用 base ace-env 路径: $BASE_ACEENV_PATH" + LOWERDIRS+=("$BASE_ACEENV_PATH") +elif [ -d "$BASE_CORE_PATH" ]; then + log.info "使用 base core 路径: $BASE_CORE_PATH" + LOWERDIRS+=("$BASE_CORE_PATH") +else + log.error "错误:基础环境路径不存在: $BASE_NAME" + exit 1 +fi + +# 添加 base 已有的 addons +_add_base_addons() { + local base_pkg_dir="$1" + local addon_file + local addon_name + local addon_dirs=() + + # 读取 info_layer_addons 主文件 + if [ -f "${base_pkg_dir}/info_layer_addons" ]; then + while IFS= read -r addon_name; do + [ -z "$addon_name" ] && continue + addon_dirs+=("$addon_name") + done < "${base_pkg_dir}/info_layer_addons" + fi + + # 读取 info_layer_addons.d 目录 + if [ -d "${base_pkg_dir}/info_layer_addons.d" ]; then + for addon_file in $(ls -1 "${base_pkg_dir}/info_layer_addons.d" 2>/dev/null | sort); do + addon_name="${addon_file#*-}" + [ -z "$addon_name" ] && continue + addon_dirs+=("$addon_name") + done + fi + + local addon + for addon in "${addon_dirs[@]}"; do + local addon_path="/var/lib/apm/${addon}" + if [ ! -d "$addon_path" ]; then + addon_path="/var/lib/apm/apm/files/ace-env/var/lib/apm/${addon}" + fi + + if [ -d "${addon_path}/files/ace-env" ]; then + log.info " 挂载已有 addon: $addon (ace-env)" + LOWERDIRS+=("${addon_path}/files/ace-env") + elif [ -d "${addon_path}/files/core" ]; then + log.info " 挂载已有 addon: $addon (core)" + LOWERDIRS+=("${addon_path}/files/core") + else + log.warn "已有 addon 路径不存在,跳过: $addon" + fi + done +} + +_add_base_addons "$BASE_DIR" + +# 将 lowerdirs 数组用冒号连接 +LOWERDIR=$(IFS=:; echo "${LOWERDIRS[*]}") +log.debug "最终 lowerdir: $LOWERDIR" + +# 3. 进行融合挂载 +log.info "正在进行融合挂载..." +sudo fuse-overlayfs \ + -o "lowerdir=$LOWERDIR,upperdir=$CRAFT_DIR/core/,workdir=$CRAFT_DIR/work/" \ + "$CRAFT_DIR/mergedir" + +if ! mountpoint -q "$CRAFT_DIR/mergedir"; then + log.error "错误:融合挂载失败" + exit 1 +fi + +log.info "挂载完成: $CRAFT_DIR/mergedir" + +# 导出 chrootEnvPath 以便 ace-run-pkg 使用 +export chrootEnvPath="$CRAFT_DIR/mergedir" +log.debug "已导出 chrootEnvPath=$chrootEnvPath" + +# 如果在手动模式下,立即打开交互 shell 并在退出后继续 +if [ "$MANUAL_MODE" = true ]; then + log.info "进入手动模式:将在融合挂载环境中打开交互 shell(使用 ace-run-pkg)。" + log.info "在 shell 中,您可以手动修改、测试安装或进行其他操作。退出 shell 后脚本将继续。" + sudo -E chrootEnvPath="$chrootEnvPath" /var/lib/apm/apm/files/ace-run-pkg bash --login || { + log.warn "ace-run-pkg shell 退出或出现错误,继续脚本..." + } + log.info "用户已退出手动 shell,脚本将继续。" + + while true; do + echo "" + read -r -p "是否现在进行 addons 包的自动打包? (y = 打包, r = 返回 shell, n = 跳过打包) [y/r/n]: " yn + case "$yn" in + y|Y) + break + ;; + r|R) + log.info "返回交互 shell(使用 ace-run-pkg)。退出 shell 后再次询问。" + sudo -E chrootEnvPath="$chrootEnvPath" /var/lib/apm/apm/files/ace-run-pkg bash --login || true + ;; + n|N) + log.info "跳过自动打包。脚本结束。" + exit 0 + ;; + *) + echo "请输入 y, r, 或 n。" + ;; + esac + done +fi + +# 4. 如果有 DEB 文件,进行安装 +if [ -n "$DEB_PATH" ]; then + log.info "在融合环境中更新包列表..." + sudo -E chrootEnvPath="$chrootEnvPath" /var/lib/apm/apm/files/ace-run-pkg aptss update || log.warn "aptss update 返回非零状态,继续但请注意" + + log.info "在融合环境中安装 DEB 包..." + if ! sudo -E chrootEnvPath="$chrootEnvPath" /var/lib/apm/apm/files/ace-run-pkg ssaudit "$DEB_PATH" --native --no-create-desktop-entry ; then + log.error "错误:DEB 包安装失败(ssaudit)" + exit 1 + fi + log.info "DEB 包安装完成(ssaudit)" + + # 清理 apt 缓存 + sudo -E chrootEnvPath="$chrootEnvPath" /var/lib/apm/apm/files/ace-run-pkg aptss clean || true + sudo -E chrootEnvPath="$chrootEnvPath" /var/lib/apm/apm/files/ace-run-pkg rm -vfr /var/lib/apt/lists || true + sudo -E chrootEnvPath="$chrootEnvPath" /var/lib/apm/apm/files/ace-run-pkg rm -vfr /var/lib/aptss/lists || true + sudo -E chrootEnvPath="$chrootEnvPath" /var/lib/apm/apm/files/ace-run-pkg rm -vfr /var/cache/apt/archives/* || true +fi + +# 5. 清理 .dpkg-new 文件 +log.info "搜索并清理 .dpkg-new 文件..." +find "$CRAFT_DIR/core" -name "*.dpkg-new" 2>/dev/null | while read -r file; do + sudo rm -vfr "$file" +done + +COUNT=$(find "$CRAFT_DIR/core" -name "*.dpkg-new" -type f 2>/dev/null | wc -l) +if [ "$COUNT" -eq 0 ]; then + log.info "已清理所有 .dpkg-new 文件" +else + log.warn "仍有 $COUNT 个 .dpkg-new 文件存在" +fi + +# 6. 准备打包参数 +ORIG_ARCH="$(dpkg --print-architecture 2>/dev/null || echo "unknown")" + +# 确定包名 +if [ -z "$PKGNAME" ]; then + if [ "$MANUAL_MODE" = true ]; then + read -r -p "请输入要创建的 addons 包名 (建议格式: ${BASE_NAME}-<描述>-addons): " PKGNAME + else + log.warn "未指定包名,使用默认格式: ${BASE_NAME}-addons" + PKGNAME="${BASE_NAME}-addons" + fi +fi + +# 确定版本 +if [ -z "$VERSION" ]; then + if [ "$MANUAL_MODE" = true ]; then + read -r -p "请输入要创建的版本 (Version) [默认 1.0.0-apm]: " VERSION + fi + VERSION="${VERSION:-1.0.0-apm}" +fi + +NEW_PKGNAME="$PKGNAME" +NEW_VERSION="$VERSION" + +log.info "将使用的新包名: $NEW_PKGNAME" +log.info "将使用的新版本: $NEW_VERSION" +log.info "使用的架构: $ORIG_ARCH" + +# 7. 创建新的 addons 包结构 +log.info "创建新的 addons 包结构..." +PKG_BUILD_DIR="$CRAFT_DIR/new-pkg" +mkdir -p "$PKG_BUILD_DIR/DEBIAN" +mkdir -p "$PKG_BUILD_DIR/var/lib/apm/$NEW_PKGNAME/files" 2>/dev/null || true + +# 复制融合环境(core, work)到新的包内 files +log.info "复制融合环境文件..." +sudo cp -r "$CRAFT_DIR"/core "$PKG_BUILD_DIR/var/lib/apm/${NEW_PKGNAME}/files/" 2>/dev/null || true +sudo cp -r "$CRAFT_DIR"/work "$PKG_BUILD_DIR/var/lib/apm/${NEW_PKGNAME}/files/" 2>/dev/null || true +sudo chmod -R 755 "$PKG_BUILD_DIR/var/lib/apm/${NEW_PKGNAME}/files/" 2>/dev/null || true + +# 创建 postinst 脚本:安装时创建 info_layer_addons.d 标记 +cat > "$PKG_BUILD_DIR/DEBIAN/postinst" << EOF +#!/bin/bash +# 创建 addons 标记 +mkdir -p /var/lib/apm/${BASE_NAME}/info_layer_addons.d +echo "${NEW_PKGNAME}" > /var/lib/apm/${BASE_NAME}/info_layer_addons.d/50-${NEW_PKGNAME} +EOF +chmod +x "$PKG_BUILD_DIR/DEBIAN/postinst" + +# 创建 postrm 脚本:卸载时删除标记和包目录 +cat > "$PKG_BUILD_DIR/DEBIAN/postrm" << EOF +#!/bin/bash +PACKAGE_NAME="\$DPKG_MAINTSCRIPT_PACKAGE" + +if [ "\$1" = "remove" ] || [ "\$1" = "purge" ]; then + echo "清理卸载残留" + rm -f "/var/lib/apm/${BASE_NAME}/info_layer_addons.d/50-\${PACKAGE_NAME}" + rm -rf "/var/lib/apm/\${PACKAGE_NAME}" + for username in \$(ls /home); do + if [ -d "/home/\${username}/.apm/\${PACKAGE_NAME}" ]; then + rm -fr "/home/\${username}/.apm/\${PACKAGE_NAME}" + fi + done +else + echo "非卸载,跳过清理" +fi +EOF +chmod +x "$PKG_BUILD_DIR/DEBIAN/postrm" + +# 8. 解除挂载(如果尚未解除) +log.info "解除挂载..." +cleanup_mount + +# 计算目录大小 +calculate_directory_size() { + local dir="$1" + if [ -d "$dir" ]; then + du -sk "$dir" | cut -f1 + else + echo "0" + fi +} + +# 创建 control 文件 +cat > "${PKG_BUILD_DIR}/DEBIAN/control" << EOF +Package: $NEW_PKGNAME +Version: $NEW_VERSION +Architecture: $ORIG_ARCH +Maintainer: APM Addons Maker +Depends: $BASE_NAME +Installed-Size: $(calculate_directory_size "$PKG_BUILD_DIR") +Description: APM addons package for ${BASE_NAME} + This package provides additional layers for the ${BASE_NAME} environment. + Addons type: ${NEW_PKGNAME#${BASE_NAME}-} +EOF + +# 9. 打包并生成输出文件名 +OUTPUT_DEB="${NEW_PKGNAME}_${NEW_VERSION}_${ORIG_ARCH}.deb" +log.info "开始使用 fakeroot 打包: $OUTPUT_DEB" +fakeroot dpkg-deb -Z xz --build "$PKG_BUILD_DIR" "$OUTPUT_DEB" || { + log.error "错误:打包 addons 包失败" + exit 1 +} + +log.info "Addons 包创建完成!" +log.info "生成的包: $OUTPUT_DEB" +log.info "包名: $NEW_PKGNAME" +log.info "版本: $NEW_VERSION" +log.info "架构: $ORIG_ARCH" +log.info "依赖: $BASE_NAME" +log.info "说明: 安装后将在 ${BASE_NAME}/info_layer_addons.d/ 中注册此 addons" + +# 退出(trap 会触发 cleanup) +exit 0 diff --git a/src/usr/bin/amber-pm-convert b/src/usr/bin/amber-pm-convert index 65d204d..f67c06b 100755 --- a/src/usr/bin/amber-pm-convert +++ b/src/usr/bin/amber-pm-convert @@ -15,18 +15,20 @@ fi # 显示用法信息 usage() { - echo "用法: $SCRIPT_NAME [--manual] --base [--base ...] " + echo "用法: $SCRIPT_NAME [--manual] --base [--base ...] [--addons ...] " echo " 或者在手动模式下不传入 DEB 文件: $SCRIPT_NAME --manual --base [--base ...]" echo "" echo "参数说明:" echo " --manual 启用手动模式:融合挂载后打开交互 shell,退出 shell 后脚本继续" echo " --basename 必填参数(非手动模式下),指定基础环境名称,可多次使用" + echo " --addons 可选参数,指定额外挂载的 addons 包名称,可多次使用" echo " deb文件路径 要转换的DEB文件路径(非手动且非空模式下必填)" echo " --pkgname 可选参数,指定新包的包名(默认使用原DEB包名)" echo " --version 可选参数,指定新包的版本号(默认在原版本后追加'-apm')" echo "" echo "示例:" echo " $SCRIPT_NAME --base amber-pm-trixie /path/to/package.deb" + echo " $SCRIPT_NAME --base amber-pm-trixie --addons amber-pm-trixie-nvidia-addons /path/to/package.deb" echo " $SCRIPT_NAME --manual --base amber-pm-trixie # 只融合挂载并进入手动 shell" echo " $SCRIPT_NAME --manual --base amber-pm-trixie --pkgname newpkg --version 1.2.3 /path/to/package.deb" echo "" @@ -36,6 +38,7 @@ usage() { # 解析参数 BASENAMES=() # 存放实际用于构建 overlay 的 base(可能会被递归添加) BASENAMES_ORIG=() # 存放用户原始输入的 base 列表(用于 control 中 Depends 等) +ADDONS=() # 存放用户显式指定的 addons 包名 DEB_PATH="" PKGNAME="" VERSION="" @@ -54,6 +57,15 @@ while [ $# -gt 0 ]; do BASENAMES_ORIG+=("$2") shift 2 ;; + --addons) + if [ -z "$2" ]; then + log.error "--addons 后需要跟 addons 包名" + usage + exit 1 + fi + ADDONS+=("$2") + shift 2 + ;; --pkgname) PKGNAME="$2" shift 2 @@ -205,7 +217,56 @@ log.info "将使用的新包名: ${NEW_PKGNAME:-<未指定>}" log.info "将使用的新版本: ${NEW_VERSION:-<未指定>}" log.info "使用的架构: $ORIG_ARCH" -# 2. 构建 lowerdir 路径(多个 base 按顺序叠放) +# 辅助函数:读取某个 base 的 addons 并追加到 LOWERDIRS +_add_base_addons_to_lowerdirs() { + local base_name="$1" + local base_dir="/var/lib/apm/apm/files/ace-env/var/lib/apm/${base_name}" + if [ ! -d "$base_dir" ]; then + base_dir="/var/lib/apm/${base_name}" + fi + [ ! -d "$base_dir" ] && return + + local addon_dirs=() + local addon_file + local addon_name + + # 读取 info_layer_addons 主文件 + if [ -f "${base_dir}/info_layer_addons" ]; then + while IFS= read -r addon_name; do + [ -z "$addon_name" ] && continue + addon_dirs+=("$addon_name") + done < "${base_dir}/info_layer_addons" + fi + + # 读取 info_layer_addons.d 目录 + if [ -d "${base_dir}/info_layer_addons.d" ]; then + for addon_file in $(ls -1 "${base_dir}/info_layer_addons.d" 2>/dev/null | sort); do + addon_name="${addon_file#*-}" + [ -z "$addon_name" ] && continue + addon_dirs+=("$addon_name") + done + fi + + local addon + for addon in "${addon_dirs[@]}"; do + local addon_path="/var/lib/apm/${addon}" + if [ ! -d "$addon_path" ]; then + addon_path="/var/lib/apm/apm/files/ace-env/var/lib/apm/${addon}" + fi + + if [ -d "${addon_path}/files/ace-env" ]; then + log.info " 自动挂载 addon: $addon (ace-env)" + LOWERDIRS+=("${addon_path}/files/ace-env") + elif [ -d "${addon_path}/files/core" ]; then + log.info " 自动挂载 addon: $addon (core)" + LOWERDIRS+=("${addon_path}/files/core") + else + log.warn "自动 addon 路径不存在,跳过: $addon" + fi + done +} + +# 2. 构建 lowerdir 路径(多个 base 按顺序叠放,包含自动 addons) log.info "构建 overlay lowerdir 路径..." LOWERDIRS=() @@ -213,6 +274,9 @@ for BASENAME in "${BASENAMES[@]}"; do ACE_ENV_PATH="/var/lib/apm/apm/files/ace-env/var/lib/apm/${BASENAME}/files/ace-env" CORE_PATH="/var/lib/apm/apm/files/ace-env/var/lib/apm/${BASENAME}/files/core" + # 先挂载该 base 的 addons(addons 在 base 之上) + _add_base_addons_to_lowerdirs "$BASENAME" + if [ -d "$ACE_ENV_PATH" ]; then log.info "使用 ace-env 路径: $ACE_ENV_PATH" LOWERDIRS+=("$ACE_ENV_PATH") @@ -227,6 +291,26 @@ for BASENAME in "${BASENAMES[@]}"; do fi done +# 追加用户显式指定的 addons(放在 bases 之上) +for addon in "${ADDONS[@]}"; do + ADDON_PATH="/var/lib/apm/${addon}" + if [ ! -d "$ADDON_PATH" ]; then + ADDON_PATH="/var/lib/apm/apm/files/ace-env/var/lib/apm/${addon}" + fi + + if [ -d "${ADDON_PATH}/files/ace-env" ]; then + log.info "使用显式 addon ace-env 路径: ${ADDON_PATH}/files/ace-env" + LOWERDIRS+=("${ADDON_PATH}/files/ace-env") + elif [ -d "${ADDON_PATH}/files/core" ]; then + log.info "使用显式 addon core 路径: ${ADDON_PATH}/files/core" + LOWERDIRS+=("${ADDON_PATH}/files/core") + else + log.error "错误:显式指定的 addon 路径不存在: $addon" + log.error " 检查的路径: ${ADDON_PATH}" + exit 1 + fi +done + # 将 lowerdirs 数组用冒号连接 LOWERDIR=$(IFS=:; echo "${LOWERDIRS[*]}") log.debug "最终 lowerdir: $LOWERDIR" @@ -751,8 +835,29 @@ calculate_directory_size() { fi } -# 构建依赖字符串 - 包含所有用户原始输入的 base(用于 control) -DEPENDS_STR=$(IFS=,; echo "${BASENAMES_ORIG[*]}") +# 构建依赖字符串 - 包含所有用户原始输入的 base 和显式指定的 addons(用于 control) +DEPENDS_PARTS=("${BASENAMES_ORIG[@]}") + +# 如果包名是 addons 格式(*-addons),确保 base 已在依赖中 +if [[ "${NEW_PKGNAME}" == *-addons ]]; then + log.info "检测到 addons 包,确保 base 依赖已包含" +fi + +# 追加显式指定的 addons 到依赖 +for addon in "${ADDONS[@]}"; do + FOUND_ADDON=false + for existing in "${DEPENDS_PARTS[@]}"; do + if [ "$existing" = "$addon" ]; then + FOUND_ADDON=true + break + fi + done + if [ "$FOUND_ADDON" = false ]; then + DEPENDS_PARTS+=("$addon") + fi +done + +DEPENDS_STR=$(IFS=,; echo "${DEPENDS_PARTS[*]}") # 若打包前没有 NEW_PKGNAME/NEW_VERSION,交互询问(一般出现在手动无DEB场景) if [ -z "$NEW_PKGNAME" ]; then diff --git a/src/usr/libexec/apm/apm-main b/src/usr/libexec/apm/apm-main index 922041f..8de64be 100755 --- a/src/usr/libexec/apm/apm-main +++ b/src/usr/libexec/apm/apm-main @@ -92,28 +92,100 @@ apm_exec(){ # =============================== local lowerdirs=() local env_layers=() + local addon_envs=() + local processed_addon_pkgs=() local current_dir="${PATH_PREFIX}/var/lib/apm/${coredir}" local next_info_file="" local APM_RUN_EXEC=/var/lib/apm/apm/files/ace-run # =============================== - # 递归读取 info / info_env + # 辅助函数:解析并添加某个包的 addons + # =============================== + _resolve_addons() { + local pkgname="$1" + # 去重检查:每个包的 addons 只处理一次 + for processed in "${processed_addon_pkgs[@]}"; do + [[ "$processed" == "$pkgname" ]] && return + done + processed_addon_pkgs+=("$pkgname") + + local pkg_dir="${PATH_PREFIX}/var/lib/apm/${pkgname}" + local all_addons=() + + # 先读取 info_layer_addons.d 目录(.d 配置优先级更高) + if [[ -d "${pkg_dir}/info_layer_addons.d" ]]; then + local addon_file + for addon_file in $(ls -1 "${pkg_dir}/info_layer_addons.d" 2>/dev/null | sort); do + local addon_name="${addon_file#*-}" + [[ -z "$addon_name" ]] && continue + all_addons+=("$addon_name") + done + fi + + # 再读取 info_layer_addons 主文件 + if [[ -f "${pkg_dir}/info_layer_addons" ]]; then + local addon_name + while IFS= read -r addon_name; do + [[ -z "$addon_name" ]] && continue + all_addons+=("$addon_name") + done < "${pkg_dir}/info_layer_addons" + fi + + local addon + for addon in "${all_addons[@]}"; do + # 在 lowerdirs 中去重 + local dup=false + local existing + for existing in "${lowerdirs[@]}"; do + if [[ "$existing" == "${PATH_PREFIX}/var/lib/apm/${addon}/files/ace-env" ]] || \ + [[ "$existing" == "${PATH_PREFIX}/var/lib/apm/${addon}/files/core" ]]; then + dup=true + break + fi + done + [[ "$dup" == true ]] && continue + + if [[ -d "${PATH_PREFIX}/var/lib/apm/${addon}/files/ace-env" ]]; then + lowerdirs+=("${PATH_PREFIX}/var/lib/apm/${addon}/files/ace-env") + elif [[ -d "${PATH_PREFIX}/var/lib/apm/${addon}/files/core" ]]; then + lowerdirs+=("${PATH_PREFIX}/var/lib/apm/${addon}/files/core") + else + log.warn "Addon layer not found: $addon" + continue + fi + + if [[ -f "${PATH_PREFIX}/var/lib/apm/${addon}/info_env" ]]; then + addon_envs+=("${PATH_PREFIX}/var/lib/apm/${addon}/info_env") + fi + done + } + + # =============================== + # 递归读取 info / info_env / addons # =============================== while : ; do next_info_file="${current_dir}/info" - # 记录 info_env(底层优先) + # 记录 info_env if [[ -f "${current_dir}/info_env" ]]; then env_layers+=("${current_dir}/info_env") fi - # 没有 info 就停止 - [[ ! -f "$next_info_file" ]] && break + # 没有 info 也处理 addons(最底层 base 也可以有 addons),然后停止 + if [[ ! -f "$next_info_file" ]]; then + local pkgname + pkgname="$(basename "$current_dir")" + _resolve_addons "$pkgname" + break + fi # 读取依赖层 while IFS= read -r basedir; do [[ -z "$basedir" ]] && continue + # 先处理该 base 的 addons(addons 在 base 之上) + _resolve_addons "$basedir" + if [[ -d "${PATH_PREFIX}/var/lib/apm/${basedir}/files/ace-env" ]]; then lowerdirs+=("${PATH_PREFIX}/var/lib/apm/${basedir}/files/ace-env") elif [[ -d "${PATH_PREFIX}/var/lib/apm/${basedir}/files/core" ]]; then @@ -168,6 +240,17 @@ apm_exec(){ fi fi + # =============================== + # 追加 addon envs(在 base env 之后,override env 之前) + # 反向追加以确保层级高的 addon env 后加载(优先级更高) + # =============================== + if [[ ${#addon_envs[@]} -gt 0 ]]; then + local i + for ((i=${#addon_envs[@]}-1; i>=0; i--)); do + env_layers+=("${addon_envs[i]}") + done + fi + # =============================== # 检查 lowerdir # ===============================