summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndrew Cady <d@jerkface.net>2017-08-30 19:20:21 -0400
committerAndrew Cady <d@jerkface.net>2017-08-30 19:20:21 -0400
commit7392932128db786fe915e8ef432953fbf55bc0f2 (patch)
tree0a3c2908dfa62d2a67ae748e85b011ad6c37c250
parentd2e4d3fb467b332fe0f972ccab2c5c37ccdd5fd5 (diff)
selfstrap: debootstrap using local /var/cache/apt/archives
-rwxr-xr-xmultistrap/selfstrap302
1 files changed, 302 insertions, 0 deletions
diff --git a/multistrap/selfstrap b/multistrap/selfstrap
new file mode 100755
index 0000000..265049b
--- /dev/null
+++ b/multistrap/selfstrap
@@ -0,0 +1,302 @@
1#!/bin/bash
2debian_mirror=http://httpredir.debian.org/debian
3
4if [ $# = 1 ]; then
5 rootfs=$1
6else
7 echo "Usage: $0 <target directory>" >&2
8 exit 1
9fi
10
11die() { printf 'Error: %s\n' "$*"; exit 1; }
12
13am_root() { [ "$(id -u)" = 0 ]; }
14
15current_debian_codename()
16{
17 # lsb_release -cs
18 sed -ne 's/^VERSION=.* (\(.*\)).*/\1/p' /etc/os-release
19}
20
21sanity_checks()
22{
23 am_root || die 'you are not root'
24 rootfs=$(realpath "$rootfs") || die 'realpath failed'
25 [ "$rootfs" ] || die 'no $rootfs'
26 [ -d "$rootfs" ] || mkdir "$rootfs" || die 'could not mkdir($rootfs)'
27}
28
29generate_apt_config()
30{
31 APT_CONFIG=$(mktemp) || exit
32 chmod 644 "$APT_CONFIG"
33 cat > "$APT_CONFIG" <<EOF
34Dir::Etc "${rootfs}/etc/apt/";
35Dir::Etc::Parts "${rootfs}/etc/apt/apt.conf.d/";
36Dir::Etc::PreferencesParts "${rootfs}/etc/apt/preferences.d/";
37EOF
38 export APT_CONFIG
39}
40
41apt_get()
42{
43 [ "$rootfs" ] || die 'no $rootfs'
44 [ "$APT_CONFIG" ] || die 'no $APT_CONFIG'
45
46 set -- -o Apt::Install-Recommends=false "$@"
47
48 # Set various paths to within the created system.
49 set -- -o Apt::Architecture="$rootfs_arch" "$@"
50 set -- -o Apt::Default-Release="$release" "$@"
51 set -- -o Dir="$rootfs" "$@"
52 set -- -o Dir::Cache="$rootfs"/var/cache/apt/ "$@"
53 set -- -o Dir::Etc::Parts="$rootfs"/etc/apt/apt.conf.d/ "$@"
54 set -- -o Dir::Etc::PreferencesParts="$rootfs"/etc/apt/preferences.d/ "$@"
55 set -- -o Dir::Etc="$rootfs"/etc/apt/ "$@"
56 set -- -o Dir::State="$rootfs"/var/lib/apt/ "$@"
57 set -- -o Dir::State::Status="$rootfs"/var/lib/dpkg/status "$@"
58
59 # We must also set dpkg to use the created system.
60 # Dpkg::options requires undocumented apt CLI magic.
61 if [ "$DEBUG_DPKG" ]; then
62 [ "$DEBUG_DPKG" -gt 0 ] 2>/dev/null || DEBUG_DPKG=013
63 set -- -o DPkg::options::arg0=--debug="${DEBUG_DPKG}" "$@"
64 fi
65 # This is the important one:
66 set -- -o DPkg::options::arg1=--root="$rootfs" "$@"
67 set -- -o DPkg::options::arg2=--force-unsafe-io "$@"
68
69 # Use the calling system for these. This is an optimization.
70 set -- -o Dir::Cache::archives=/var/cache/apt/archives "$@"
71 set -- -o Dir::Etc::Trusted=/etc/apt/trusted.gpg "$@"
72 set -- -o Dir::Etc::TrustedParts=/etc/apt/trusted.gpg.d "$@"
73 set -- -o Dir::State::Lists=/var/lib/apt/lists "$@"
74
75 # Avoid deleting lists on the calling system.
76 set -- -o APT::Get::List-Cleanup=false "$@"
77
78 apt-get "$@"
79}
80
81write_lines_once()
82{
83 local output="$1"
84 shift
85 [ -e "$output" ] || printf '%s\n' "$@" > "$output"
86}
87
88populate_rootfs()
89{
90 [ "$rootfs" ] || die 'no $rootfs'
91 set -B
92 mkdir -p \
93 "$rootfs"/etc/apt/{preferences.d,apt.conf.d,trusted.gpg.d,sources.list.d} \
94 "$rootfs"/var/{lib/{apt/lists/partial,dpkg/{info,parts,triggers,alternatives,updates}},cache/apt} \
95 "$rootfs"/var/log/apt
96
97 touch "$rootfs"/var/lib/dpkg/status
98
99 write_lines_once "$rootfs"/var/lib/dpkg/arch "$rootfs_arch"
100 write_lines_once "$rootfs"/etc/apt/sources.list \
101 "deb $debian_mirror $release main contrib non-free" \
102 "deb http://security.debian.org $release/updates main contrib non-free"
103}
104
105parse_apt_noact_line()
106{
107 set -- $*
108 action=$1
109 package=$2
110 version=${3#\(}
111 shift 3
112
113 while [ "$1" ]; do
114 case "$1" in
115 *\))
116 arch=${1%\)}
117 arch=${arch#\[}
118 arch=${arch%\]}
119 return
120 ;;
121 *) shift ;;
122 esac
123 done
124 return 1
125}
126
127extract_dpkg_info()
128{
129 local deb="$1" TARGET="$2" multiarch="$3" command PKG
130 PKG=${deb##*/}
131 PKG=${PKG%%_*}
132 PKG=$PKG$multiarch
133
134 command='
135[ "$TAR_FILENAME" = ./control ] && exit
136f=$TARGET/var/lib/dpkg/info/$PKG.${TAR_FILENAME#./}
137cat > "$f"
138chmod $TAR_MODE "$f"
139'
140 (export PKG TARGET
141 dpkg --ctrl-tarfile "$deb" | tar -x --to-command "$command")
142 dpkg --fsys-tarfile "$deb" | tar -t | sed 's?^\.??; s?^/$?/.?; s?/$??' > "$TARGET/var/lib/dpkg/info/$PKG.list"
143
144}
145
146apt_run_inst()
147{
148 apt_get -s -yqq install "$@" | while read line; do
149 parse_apt_noact_line "$line" || die "unexpected output from apt-get: $line"
150 export DPKG_MAINTSCRIPT_PACKAGE="$package" DPKG_MAINTSCRIPT_ARCH="$arch"
151 if is_multiarch_same "$package"; then
152 multiarch=":$arch"
153 else
154 multiarch=
155 fi
156 case "$action" in
157 Inst)
158 export DPKG_MAINTSCRIPT_NAME=preinst
159 preinst=/var/lib/dpkg/info/${package}${multiarch}.preinst
160 if [ -x "$rootfs"/"$preinst" ]; then
161 (set -x; chroot "$rootfs" "$preinst" install)
162 fi
163 ;;
164 Conf)
165 export DPKG_MAINTSCRIPT_NAME=postinst
166 postinst=/var/lib/dpkg/info/${package}${multiarch}.postinst
167 if [ -x "$rootfs"/"$postinst" ]; then
168 (set -x; chroot "$rootfs" "$postinst" configure)
169 fi
170 ;;
171 Remv) ;;
172 *) die "impossible" ;;
173 esac
174 done
175}
176
177apt_extract()
178{
179 apt_get -d -yqq install "$@"
180 apt_get -s -yqq install "$@" | while read line; do
181 parse_apt_noact_line "$line" || die "unexpected output from apt-get: $line"
182
183 deb=/var/cache/apt/archives/${package}_${version//:/%3a}_${arch}.deb
184 [ -f "$deb" ] || {
185 echo "line=$line" >&2
186 printf '%s\n' "$action" "$package" "$version" "$arch" >&2
187 die "unexpected output from apt-get"
188 }
189 case "$action" in
190 Inst)
191 printf 'Extracting %s\n' "${deb##*/}" >&2
192 dpkg-deb --extract "$deb" "$rootfs"
193
194 if [ "$EXTRACT_DPKG_INFO" ]; then
195 is_multiarch_same "$package" && multiarch=":$arch" || multiarch=
196 extract_dpkg_info "$deb" "$rootfs" "$multiarch"
197 fi
198 ;;
199 Conf)
200 ;;
201 Remv) ;;
202 *) die "impossible" ;;
203 esac
204 done
205}
206
207install_etc_passwd()
208{
209 [ -e "$rootfs"/etc/passwd ] || cp "$rootfs"//usr/share/base-passwd/passwd.master "$rootfs"/etc/passwd
210 [ -e "$rootfs"/etc/group ] || cp "$rootfs"//usr/share/base-passwd/group.master "$rootfs"/etc/group
211}
212
213main_packages_file()
214{
215 local uri_part="${debian_mirror#*/}"
216 while [ "${uri_part:0:1}" = / ]; do
217 uri_part=${uri_part#?}
218 done
219 uri_part=${uri_part//\//_}
220 printf '/var/lib/apt/lists/%s_dists_%s_main_binary-%s_Packages\n' "$uri_part" "$release" "$rootfs_arch"
221}
222
223required_packages()
224{
225 perl -00 -ne \
226 '/^Priority: required/m || next; /^Package: (.*)$/m && print "$1\n"' \
227 "$(main_packages_file)" | sort -u
228}
229
230multiarch_same_packages()
231{
232 [ "$multiarch_same_packages" ] || multiarch_same_packages=$(multiarch_same_packages_)
233 echo "$multiarch_same_packages"
234}
235
236multiarch_same_packages_()
237{
238 perl -00 -ne \
239 '/^Multi-Arch: same/mi || next; /^Package: (.*)$/m && print "$1\n"' \
240 "$(main_packages_file)" | sort -u
241}
242
243is_multiarch_same()
244{
245 local p
246 for p in $(multiarch_same_packages); do
247 [ "$1" = "$p" ] && return
248 done
249 return 1
250}
251
252set -e
253
254rootfs_arch=$(dpkg-architecture -q DEB_HOST_ARCH) || die 'dpkg-architecture failed'
255release=$(current_debian_codename) && [ "$release" ] || die 'could not determine Debian release name'
256
257if [ "$FAST_MODE" ]; then
258 SKIP_INSTALL=y
259 EXTRACT_DPKG_INFO=y
260 RUN_INST_SCRIPTS=y
261fi
262
263# Set things up so apt-get update works.
264sanity_checks
265generate_apt_config
266populate_rootfs
267
268
269# Initial apt-get update will determine what we shall install.
270[ "$SKIP_UPDATE" ] || apt_get update
271packages=$(required_packages) && [ "$packages" ] || die 'failed to determine list of required packages'
272extra_packages='apt debian-archive-keyring locales'
273
274
275# Extract files from downloaded packages.
276#
277# Some files need to be present before 'apt-get install' can install anything.
278# In particular:
279#
280# 1. binaries used by dpkg 'inst' scripts.
281# 2. /etc/passwd and /etc/group so that 'chown' works
282
283# Rather than fuss about (1), extract everything from all packages.
284apt_extract $packages
285# This handles (2).
286install_etc_passwd
287
288
289# Finally we are ready to run apt-get install.
290export LC_ALL=C
291export DEBIAN_FRONTEND=noninteractive
292if [ "$SKIP_INSTALL" ]; then
293 apt_extract $extra_packages
294 if [ "$RUN_INST_SCRIPTS" ]; then
295 apt_run_inst $packages $extra_packages
296 fi
297else
298 if [ "$FIX_BROKEN" ]; then
299 dpkg --root="$rootfs" --configure -a
300 fi
301 apt_get install -y $packages $extra_packages
302fi