(TL;DR:在给定初始状态和所需的软件包列表的情况下,如何确定要安装的最小软件包集?)
我正在为无法访问分发镜像的系统创建 Ubuntu 安装程序 ISO。这些是嵌入式设备,只需要基础包和一些本地构建的包加上它们的所有依赖项。例如,我们使用的是 Qt/Xcb,因此libqt5*
需要引入各种包。
目前,我正在使用强力方法来确定将哪些内容放入安装程序的 pool/extras 目录中。首先,我使用在回答 debconf 时提到的软件包作为种子d-i pkgsel/include
,然后使用以下命令查找递归依赖项bash
:
# 1. Extract the dependencies from packages/*, removing version
# conditions and changing separator to newline.
# 2. Find the recursive dependencies of all these packages using
# package database, extracting Depends and PreDepends.
# 3. For each package we require:
# a. if we find it in our cache, just copy it into the ISO (using
# hard link for speed and space)
# b. else, if we find it already in the ISO, do nothing
# c. else, pass it through to step 4.
# 4. Search for matching packages in repositories that are not virtual
# or foreign-architecture.
# 5. Download matching package files to the ISO.
(for i in "$TOP/packages"/*.deb; do dpkg-deb -f $i depends; done) | sed 's/([^()]*)//g;s/[|,]/\n/g' \
| xargs -r apt-cache --recurse --important depends | sed -re 's/^ (Pre)?Depends://;/[<|:]/d;s/^ *//;' | sort -u \
| ( while read p; \
do test $(find "$TOP/extras" -name "${p}_*.deb" -exec mv '{}' "$REMASTER_HOME"/remaster-iso/pool/extras \; -printf "true" -quit) \
|| test $(find "$REMASTER_HOME"/remaster-iso/pool/{main,extras} -name "${p}_*.deb" -printf "true" -quit) \
|| echo $p; \
done; ) \
| uniq | sed -r 's/[^ ]+/!~rforeign!~v^&$/' | xargs -r aptitude -q2 -F '%p' search \
| (cd "$REMASTER_HOME"/remaster-iso/pool/extras; xargs -r apt-get download)
这里,$TOP/packages
是种子包目录,$TOP/extras
是以前下载的包的缓存,$REMASTER_HOME"/remaster-iso
是解压后的安装程序 ISO 的根目录。
虽然上述方法有效,但它往往会获取比我们实际需要的更多的内容,因为apt-cache depends
报告了每个包的所有替代依赖项。例如,grub-pc
取决于debconf
;cdebconf
提供debconf
,因此 和debconf
以及cdebconf
它们的依赖项都包含在内。另外,xserver-xorg
取决于xserver-xorg-video-all | xorg-driver-video
,所以我的脚本会提取所有视频驱动程序 - 但我已经声明了对 的依赖关系xserver-xorg-video-fbdev
,所以我希望它能够满足其自身的要求。
我想要的是相当于aptitude --download-only install ${MY_PACKAGES}
在目标系统上运行。但是,当然,在构建主机上运行它会丢失大多数软件包,因为它们已经安装在那里。
我目前正在查看python-apt
库,但我找不到任何为目标系统而不是运行它的主机初始化它的示例。有没有人做过类似的事情,我可以复制一下?
或者,还有其他可行的方法吗?我正在考虑创建一个原始的、最小的 chroot 并aptitude
在其中运行命令,但我担心这将成为维护负担,并且其他人很难在自己的计算机上进行设置。
我希望可以使用该解决方案(具有新的初始状态)来在要更新任何包时找到必要的依赖项,因为我需要为目标系统的可移动介质写入一致的集(仍然没有访问分发镜像)。
答案1
debootstrap
在目录中建立一个干净的新系统,使用 执行该目录中所需的操作chroot
,然后获取在 中找到的所有内容chrootdir/var/cache/apt/archives/
。
在第二个答案后编辑:我刚刚检查了手册,似乎也可以用来debootstrap
做大部分(如果不是全部)你想要的事情:它可以在 下运行fakeroot
,并且它有--download-only
和--print-debs
选项以及--include
将其他包添加到也混合并解决它们的依赖关系。
在阅读源代码之前不确定是否使用两组不同的源代码 - 它似乎也支持这一点:)
例如:
% fakeroot /usr/sbin/debootstrap --variant=minbase --download-only --include=dnsutils wheezy /tmp/foo "http://ftp.us.debian.org/debian file:///local_packages_dir/"
[...]
I: Resolving dependencies of required packages...
I: Resolving dependencies of base packages...
I: Found additional required dependencies: [...]
I: Found additional base dependencies: [...]
[...]
有一点开销:
% du -sh /tmp/foo/var/cache/apt/archives/
40M /tmp/foo/var/cache/apt/archives/
% du -sh /tmp/foo/
68M /tmp/foo/
实际的下载可以使用 apt-mirror 之类的工具进行优化,这样就不会每次都从远程位置下载相同的文件。
答案2
这是一个bash
脚本而不是python-apt
,但我发现dpkg-offline
其中包含类似的逻辑。我研究了它的工作原理,并成功创建了一个自己的脚本来进行必要的下载 - 可重复并作为普通非 root 用户:
#!/bin/bash
set -e
die() {
exec >&2
printf '%s\n' "$@"
exit 1
}
test "$UID" != "0" || die \
"Do not run '$0' as root, because it will break your packages."
test -n "$1" || die \
"Usage: $0 <package-dir> [package] ..." \
" package may be followed by _ - + &m etc; like 'aptitude install'" \
" local packages must be listed in package-dir/Packages.gz" \
" downloaded packages are saved to package-dir/ubuntu"
PACKAGE_DIR="$1"; shift
BASE_DIR=$(mktemp -d)
trap 'rm -r "$BASE_DIR"' EXIT
(
cd $BASE_DIR
# Skeleton directory structure
mkdir -p \
etc/apt/preferences.d \
etc/apt/sources.list.d \
var/cache/apt/archives \
var/lib/apt/lists \
var/lib/aptitude \
var/lib/dpkg \
var/log
# Use GPG keys from host, and create empty dpkg status
cp /etc/apt/trusted.gpg etc/apt
touch var/lib/dpkg/status
cat >etc/apt/sources.list <<-EOF
# Locally-built packages
deb file://$PACKAGE_DIR /
# Upstream distribution to pillage
deb http://gb.archive.ubuntu.com/ubuntu/ trusty main restricted universe
EOF
# Don't let user's ~/.aptitude/* influence anything
export HOME="$BASE_DIR"
APT_OPTIONS=(-q2 \
-o "Apt::Architecture=amd64" \
-o "Apt::Install-Recommends=0" \
-o "Dir::Etc=$BASE_DIR/etc/apt" \
-o "Dir::State=$BASE_DIR/var/cache/apt" \
-o "Dir::Cache=$BASE_DIR/var/cache/apt" \
-o "Dir::Cache::Archives=$BASE_DIR/var/cache/apt/archives" \
-o "Dir::Log=$BASE_DIR/var/log" \
-o "Dir::State::Lists=$BASE_DIR/var/lib/apt/lists" \
-o "Dir::State::status=$BASE_DIR/var/lib/dpkg/status" \
-o "DPkg::Options::--admindir=$BASE_DIR/var/lib/dpkg" \
)
APTITUDE="aptitude ${APT_OPTIONS[@]}"
$APTITUDE update
$APTITUDE --download-only -y --allow-untrusted install "$@"
)
# We now have all required package files download into cache; put them
# into output directory.
ln -t "$PACKAGE_DIR/ubuntu" -vf var/cache/apt/archives/*.deb
# Create updated package index
( cd "$PACKAGE_DIR" && dpkg-scanpackages -m . | gzip -c >Packages.gz )
我可能可以使用它来创建一个 Python 程序来执行相同的操作,但 shell 脚本可能足以满足我的目的。