#!/bin/bash VERSION=@VERSION@ # 获取脚本名称用于帮助信息 SCRIPT_NAME=$(basename "$0") PATH_PREFIX=/var/lib/apm/apm/files/ace-env/ 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"; } # 帮助信息函数 show_help() { cat < [args...] 启动软件包(通过应用启动器) run [EXEC_PATH] [args...] 运行指定软件包的可执行文件(可指定容器内路径) update 更新软件包信息 list 查看可用软件包信息 search 搜索软件包 show 展示包信息 clean 清除缓存软件包 autoremove 自动移除不需要的包 amber 彩蛋功能 xmp360 彩蛋功能 bronya 彩蛋功能 -h, --help 显示此帮助信息 --help-all 显示完整帮助信息 -v, --version 展示APM版本号 本 APM 具有兔兔伯爵,女武神装甲和超级大运之力。 EOF } # 完整帮助信息函数 show_help_all() { cat < [args...] 启动软件包(通过应用启动器) run [EXEC_PATH] [args...] 运行指定软件包的可执行文件(可指定容器内路径) sandbox-run [EXEC_PATH] [args...] 运行指定软件包的可执行文件(主目录沙箱化) bwrap-run [EXEC_PATH] [args...] 运行指定软件包的可执行文件(使用 bwrap) update 更新软件包信息 hold 锁定软件包版本 unhold 解锁软件包版本 full-upgrade 升级全部软件包 list 查看可用软件包信息 search 搜索软件包 download 下载包 show 展示包信息 clean 清除缓存软件包 autoremove 自动移除不需要的包 ssinstall 使用 ssinstall 进行本地软件安装,详情见 spark-store ssaudit 使用 ssaudit 进行本地软件安装,详情见 spark-store debug 显示调试系统信息并进入调试环境 amber 彩蛋功能 xmp360 彩蛋功能 bronya 彩蛋功能 -h, --help 显示简要帮助信息 --help-all 显示此完整帮助信息 -v, --version 展示APM版本号 本 APM 具有兔兔伯爵,女武神装甲和超级大运之力。 EOF } 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 # =============================== # 辅助函数:解析并添加某个包的 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 if [[ -f "${current_dir}/info_env" ]]; then env_layers+=("${current_dir}/info_env") fi # 没有 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 lowerdirs+=("${PATH_PREFIX}/var/lib/apm/${basedir}/files/core") else log.warn "Neither ace-env nor core directory found for base: $basedir" fi done < "$next_info_file" # 递归到下一个 local next_basedir next_basedir="$(tail -n 1 "$next_info_file")" [[ -z "$next_basedir" || ! -d "${PATH_PREFIX}/var/lib/apm/${next_basedir}" ]] && break current_dir="${PATH_PREFIX}/var/lib/apm/${next_basedir}" done # =============================== # info_layer_override(最高优先级) # =============================== local override_file="${PATH_PREFIX}/var/lib/apm/${coredir}/info_layer_override" if [[ -f "$override_file" ]]; then log.debug "Found info_layer_override: $override_file" local override_dirs=() local override_envs=() while IFS= read -r basedir; do [[ -z "$basedir" ]] && continue local base="${PATH_PREFIX}/var/lib/apm/${basedir}" if [[ -d "${base}/files/ace-env" ]]; then override_dirs+=("${base}/files/ace-env") elif [[ -d "${base}/files/core" ]]; then override_dirs+=("${base}/files/core") else log.warn "Override layer not found: $basedir" fi if [[ -f "${base}/info_env" ]]; then override_envs+=("${base}/info_env") fi done < "$override_file" # override 层放最前(最高) if [[ ${#override_dirs[@]} -gt 0 ]]; then lowerdirs=("${override_dirs[@]}" "${lowerdirs[@]}") fi # override env 最后应用(最高) if [[ ${#override_envs[@]} -gt 0 ]]; then env_layers+=("${override_envs[@]}") 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 # =============================== if [[ ${#lowerdirs[@]} -eq 0 ]]; then log.error "No valid lower directories found for package: $coredir" return 1 fi local lowerdir lowerdir=$(IFS=:; echo "${lowerdirs[*]}") mkdir -p "/tmp/apm/${coredir}" # =============================== # 应用 info_env(从下到上) # =============================== for env_file in "${env_layers[@]}"; do log.debug "Applying env: $env_file" while IFS= read -r line || [[ -n "$line" ]]; do [[ -z "$line" || "$line" =~ ^[[:space:]]*# ]] && continue if [[ "$line" =~ ^[A-Za-z_][A-Za-z0-9_]*= ]]; then local key="${line%%=*}" local val="${line#*=}" # 去首尾空白 val="${val#"${val%%[![:space:]]*}"}" val="${val%"${val##*[![:space:]]}"}" # 去外层引号 if [[ "$val" =~ ^\".*\"$ || "$val" =~ ^\'.*\'$ ]]; then val="${val:1:-1}" fi export "$key=$val" else log.warn "Invalid env line ignored: $line" fi done < "$env_file" done # =============================== # 挂载 overlay # =============================== log.debug "Mounting overlayfs" log.debug "lowerdir=$lowerdir" fuse-overlayfs \ -o lowerdir="$lowerdir",upperdir="${PATH_PREFIX}/var/lib/apm/${coredir}/files/core/",workdir="${PATH_PREFIX}/var/lib/apm/${coredir}/files/work/" \ "/tmp/apm/${coredir}" # =============================== # 执行 # =============================== chrootEnvPath="/tmp/apm/${coredir}" "${APM_RUN_EXEC}" "$@" # =============================== # 卸载 # =============================== umount "/tmp/apm/${coredir}" } # 启动应用:通过 amber-pm-app-launcher 运行 apm_launch() { local pkg="$1" shift if [ -z "$pkg" ]; then log.error "Package name required for 'launch' command" return 1 fi # 保存原始 PATH_PREFIX,检查包是否存在(逻辑同 run 分支) local original_path_prefix="$PATH_PREFIX" if ! [ -d "${PATH_PREFIX}/var/lib/apm/$pkg" ]; then if [ -d "/var/lib/apm/$pkg" ]; then PATH_PREFIX="" else log.error "Package not installed: $pkg" return 1 fi fi # 调用应用启动器,传递所有参数 amber-pm-app-launcher "$pkg" "$@" local exit_code=$? # 恢复 PATH_PREFIX(不影响后续命令) PATH_PREFIX="$original_path_prefix" return $exit_code } # 调试信息函数 debug_info() { log.debug "======= APM Debug Information =======" log.debug "User: $(whoami)" log.debug "Hostname: $(hostname)" log.debug "OS: $(lsb_release -ds 2>/dev/null || uname -om)" log.debug "Kernel: $(uname -sr)" log.debug "Bash Version: ${BASH_VERSION}" log.debug "APT Version: $(apt --version | head -n1)" log.debug "APM APT Version: $(amber-pm-debug apt --version | head -n1)" log.debug "=====================================" amber-pm-debug "$@" } apm-nvidia-toggle(){ # APM 基础路径 APM_BASE="${PATH_PREFIX}/var/lib/apm" # 检查基础目录是否存在 if [[ ! -d "$APM_BASE" ]]; then echo "错误: 目录 $APM_BASE 不存在" exit 1 fi # 遍历 /var/lib/apm 下的所有目录 for dir in "$APM_BASE"/*/; do # 移除末尾的斜杠得到纯目录名 dir="${dir%/}" # 提取目录名(不包括完整路径) dirname=$(basename "$dir") # 检查目标文件是否存在 target_file="${APM_BASE}/${dirname}/files/ace-env" if [[ -e "$target_file" ]]; then # 将目录传递给 amber-pm-configure-nvidia amber-pm-configure-nvidia "$target_file" fi done } # 主命令处理 case "$1" in install|full-upgrade|upgrade|reinstall) command=$1 shift amber-pm-debug aptss "$command" "$@" exit_code=$? # 如果第一次执行失败,尝试修复并重试 if [ $exit_code -ne 0 ]; then log.warn "Command failed, attempting to fix with dpkg --configure -a..." amber-pm-debug dpkg --configure -a log.info "Retrying $command..." amber-pm-debug aptss "$command" "$@" exit_code=$? fi if [ $exit_code -eq 0 ]; then log.info "Operation successful" else log.error "Error: Operation failed" exit $exit_code fi amber-pm-debug amber-pm-dstore-patch apm-nvidia-toggle amber-pm-desktop-fix update-mime-database /var/lib/apm/apm/files/ace-env/amber-ce-tools/data-dir/mime > /dev/null 2>&1 & ;; download|search|policy|list|update|clean|show|depends|rdepends|changelog|moo) command=$1 shift amber-pm-debug aptss "$command" "$@" exit_code=$? if [ $exit_code -eq 0 ]; then log.info "Operation successful" else log.error "Error: Operation failed" exit $exit_code fi ;; hold|unhold) command=$1 shift amber-pm-debug apt-mark "$command" "$@" exit_code=$? if [ $exit_code -eq 0 ]; then log.info "Operation successful" else log.error "Error: Operation failed" exit $exit_code fi ;; remove|autoremove|purge|autopurge) # 特殊APT命令:移除第一个参数后传递其余参数 command=$1 shift amber-pm-debug aptss "$command" "$@" exit_code=$? if [ $exit_code -eq 0 ]; then log.info "Operation successful" else log.error "Error: Operation failed" exit $exit_code fi amber-pm-debug amber-pm-dstore-patch amber-pm-desktop-fix ;; launch) shift apm_launch "$@" exit_code=$? if [ $exit_code -eq 0 ]; then log.info "Operation successful" else log.error "Error: Operation failed" exit $exit_code fi ;; run) # 运行包命令:第二个参数必须是包名 if [ -z "$2" ]; then log.error "Package name required for 'run' command" show_help exit 1 fi # 检查包是否已安装 pkg="$2" shift 2 # 移除 'run' 和包名 if ! ls "${PATH_PREFIX}/var/lib/apm/$pkg" >/dev/null 2>&1; then # 如果带前缀的目录不存在,尝试不带前缀的目录 if ls "/var/lib/apm/$pkg" >/dev/null 2>&1; then # 如果不带前缀的目录存在,清空 PATH_PREFIX PATH_PREFIX="" else # 如果两个目录都不存在,报错退出 log.error "Package not installed: $pkg" exit 1 fi fi coredir=$pkg export APM_PKG_NAME=$pkg # 检测是否有额外命令参数 if [ $# -gt 0 ]; then # 有额外参数:执行用户提供的命令 log.info "Running user command: $*" apm_exec "$@" else # 没有额外参数:提示用户改用 launch,并自动调用 launch log.info "未指定可执行文件路径。如果希望在未指定容器路径的情况下启动应用程序,推荐使用 \"launch\" 命令" log.info "正在启动:$SCRIPT_NAME launch $pkg" apm_launch "$pkg" exit $? fi ;; sandbox-run) # 运行包命令:第二个参数必须是包名 export APM_USE_SANDBOX=1 shift "$0" run "$@" ;; bwrap-run) # 运行包命令:使用特殊的挂载参数以支持bwrap export APM_USE_BWRAP=1 shift "$0" run "$@" ;; debug) shift debug_info $@ ;; ssaudit) amber-pm-debug dpkg --configure -a amber-pm-debug ssaudit $@ --native exit_code=$? if [ $exit_code -eq 0 ]; then log.info "Operation successful" else log.error "Error: Operation failed" exit $exit_code fi amber-pm-debug amber-pm-dstore-patch amber-pm-desktop-fix ;; ssinstall) amber-pm-debug dpkg --configure -a amber-pm-debug ssinstall $@ --native exit_code=$? if [ $exit_code -eq 0 ]; then log.info "Operation successful" else log.error "Error: Operation failed" exit $exit_code fi amber-pm-debug amber-pm-dstore-patch amber-pm-desktop-fix ;; -h|--help) show_help ;; --help-all) show_help_all ;; -v|--version) echo "$VERSION" ;; amber) source /usr/libexec/apm/apm-eggs amber_egg ;; xmp360) source /usr/libexec/apm/apm-eggs xmp360_egg ;; bronya) source /usr/libexec/apm/apm-eggs bronya_egg ;; *) show_help ;; esac