summaryrefslogtreecommitdiff
path: root/src/selfstrap
diff options
context:
space:
mode:
authorAndrew Cady <d@jerkface.net>2017-08-30 23:55:22 -0400
committerAndrew Cady <d@jerkface.net>2017-08-30 23:55:32 -0400
commit9c9afa2abaa73c73bcb0af55b42394c54d65d710 (patch)
tree67abca16b6901c6e491b4dae36a708e12281b4b5 /src/selfstrap
parenta75ccbd6dd458ac5234ba59437550da27aaa44b4 (diff)
rename file
Diffstat (limited to 'src/selfstrap')
-rwxr-xr-xsrc/selfstrap376
1 files changed, 376 insertions, 0 deletions
diff --git a/src/selfstrap b/src/selfstrap
new file mode 100755
index 0000000..165a921
--- /dev/null
+++ b/src/selfstrap
@@ -0,0 +1,376 @@
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 visible_args="$*"
47
48 set -- -o Apt::Install-Recommends=false "$@"
49
50 # Set various paths to within the created system.
51 set -- -o Apt::Architecture="$rootfs_arch" "$@"
52 set -- -o Apt::Default-Release="$release" "$@"
53 set -- -o Dir="$rootfs" "$@"
54 set -- -o Dir::Cache="$rootfs"/var/cache/apt/ "$@"
55 set -- -o Dir::Etc::Parts="$rootfs"/etc/apt/apt.conf.d/ "$@"
56 set -- -o Dir::Etc::PreferencesParts="$rootfs"/etc/apt/preferences.d/ "$@"
57 set -- -o Dir::Etc="$rootfs"/etc/apt/ "$@"
58 set -- -o Dir::State="$rootfs"/var/lib/apt/ "$@"
59 set -- -o Dir::State::Status="$rootfs"/var/lib/dpkg/status "$@"
60
61 # We must also set dpkg to use the created system.
62 # Dpkg::options requires undocumented apt CLI magic.
63 if [ "$DEBUG_DPKG" ]; then
64 [ "$DEBUG_DPKG" -gt 0 ] 2>/dev/null || DEBUG_DPKG=10013
65 set -- -o DPkg::options::arg0=--debug="${DEBUG_DPKG}" "$@"
66 fi
67 # This is the important one:
68 set -- -o DPkg::options::arg1=--root="$rootfs" "$@"
69 set -- -o DPkg::options::arg2=--force-unsafe-io "$@"
70
71 # Use the calling system for these. This is an optimization.
72 set -- -o Dir::Cache::archives=/var/cache/apt/archives "$@"
73 set -- -o Dir::Etc::Trusted=/etc/apt/trusted.gpg "$@"
74 set -- -o Dir::Etc::TrustedParts=/etc/apt/trusted.gpg.d "$@"
75 set -- -o Dir::State::Lists=/var/lib/apt/lists "$@"
76
77 # Avoid deleting lists on the calling system.
78 set -- -o APT::Get::List-Cleanup=false "$@"
79
80 [ "$VERBOSE" ] && printf '+ apt-get %s\n' "$visible_args" >&2 || true
81
82 apt-get "$@"
83}
84
85idem() { if [ ! -e "${!#}" ]; then "$@"; fi; }
86idem_mknod() { if [ ! -e "$3" ]; then mknod "$@"; fi; }
87
88# This function is copied (with modifications) from debootstrap.
89install_devices()
90{
91 local TARGET="$rootfs"
92 [ "$TARGET" -a -d "$TARGET" ] || die 'no $TARGET'
93 idem mkdir "$TARGET"/dev
94 # The list of devices that can be created in a container comes from
95 # src/core/cgroup.c in the systemd source tree.
96 idem_mknod -m 666 "$TARGET"/dev/null c 1 3
97 idem_mknod -m 666 "$TARGET"/dev/zero c 1 5
98 idem_mknod -m 666 "$TARGET"/dev/full c 1 7
99 idem_mknod -m 666 "$TARGET"/dev/random c 1 8
100 idem_mknod -m 666 "$TARGET"/dev/urandom c 1 9
101 idem_mknod -m 666 "$TARGET"/dev/tty c 5 0
102 idem mkdir "$TARGET"/dev/pts/
103 idem mkdir "$TARGET"/dev/shm/
104 # Inside a container, we might not be allowed to create /dev/ptmx.
105 # If not, do the next best thing.
106 if ! idem_mknod -m 666 "$TARGET"/dev/ptmx c 5 2; then
107 idem ln -s pts/ptmx "$TARGET"/dev/ptmx
108 fi
109 idem ln -s /proc/self/fd "$TARGET"/dev/fd
110 idem ln -s /proc/self/fd/0 "$TARGET"/dev/stdin
111 idem ln -s /proc/self/fd/1 "$TARGET"/dev/stdout
112 idem ln -s /proc/self/fd/2 "$TARGET"/dev/stderr
113}
114
115mount_virtfs()
116{
117 local TARGET="$1"
118 [ "$TARGET" ] || die 'no $TARGET'
119 [ -d "$TARGET"/proc ] || mkdir "$TARGET"/proc
120 [ -d "$TARGET"/sys ] || mkdir "$TARGET"/sys
121 mount -t proc proc "$TARGET"/proc
122 mount -t sysfs sysfs "$TARGET"/sys
123}
124
125umount_virtfs()
126{
127 local TARGET="$1" fail=
128 [ "$TARGET" ] || die 'no $TARGET'
129 umount "$TARGET"/proc || fail=y
130 umount "$TARGET"/sys || fail=y
131 [ ! "$fail" ]
132}
133
134write_lines_once()
135{
136 local output="$1"
137 shift
138 [ -e "$output" ] || printf '%s\n' "$@" > "$output"
139}
140
141populate_rootfs()
142{
143 [ "$rootfs" ] || die 'no $rootfs'
144 set -B
145 mkdir -p \
146 "$rootfs"/etc/apt/{preferences.d,apt.conf.d,trusted.gpg.d,sources.list.d} \
147 "$rootfs"/var/{lib/{apt/lists/partial,dpkg/{info,parts,triggers,alternatives,updates}},cache/apt} \
148 "$rootfs"/var/log/apt
149
150 touch "$rootfs"/var/lib/dpkg/status
151
152 write_lines_once "$rootfs"/var/lib/dpkg/arch "$rootfs_arch"
153 write_lines_once "$rootfs"/etc/apt/sources.list \
154 "deb $debian_mirror $release main contrib non-free" \
155 "deb http://security.debian.org $release/updates main contrib non-free"
156 install_devices
157}
158
159parse_apt_noact_line()
160{
161 set -- $*
162 action=$1
163 package=$2
164 version=${3#\(}
165 shift 3
166
167 while [ "$1" ]; do
168 case "$1" in
169 *\))
170 arch=${1%\)}
171 arch=${arch#\[}
172 arch=${arch%\]}
173 return
174 ;;
175 *) shift ;;
176 esac
177 done
178 return 1
179}
180
181dpkg_extract_with_info()
182{
183 local deb="$1" TARGET="$2" multiarch="$3" command PKG
184 PKG=${deb##*/}
185 PKG=${PKG%%_*}
186 PKG=$PKG$multiarch
187
188 command=$(cat <<'EOF'
189if [ "$TAR_FILENAME" = ./control ]; then
190 (sed "/^Package:/a Status: install ok installed"; echo) >> "$TARGET"/var/lib/dpkg/status
191else
192 f=$TARGET/var/lib/dpkg/info/$PKG.${TAR_FILENAME#./}
193 cat > "$f"
194 chmod $TAR_MODE "$f"
195fi
196EOF
197)
198 (export PKG TARGET; dpkg --ctrl-tarfile "$deb" | tar -x --to-command "$command")
199 dpkg --fsys-tarfile "$deb" |
200 (cd "$TARGET" && tar -xv) |
201 sed 's?^\.??; s?^/$?/.?; s?/$??' > "$TARGET/var/lib/dpkg/info/$PKG.list"
202}
203
204apt_run_inst()
205{
206 apt_get -s -yqq install "$@" | dpkg_inst_from_apt
207}
208
209dpkg_inst_from_apt()
210{
211 while read line; do
212 parse_apt_noact_line "$line" || die "unexpected output from apt-get: $line"
213 export LC_ALL=C
214 export DEBIAN_FRONTEND=noninteractive
215 export DPKG_MAINTSCRIPT_PACKAGE="$package" DPKG_MAINTSCRIPT_ARCH="$arch"
216 is_multiarch_same "$package" && multiarch=":$arch" || multiarch=
217 case "$action" in
218 Inst)
219 export DPKG_MAINTSCRIPT_NAME=preinst
220 preinst=/var/lib/dpkg/info/${package}${multiarch}.preinst
221 if [ -x "$rootfs"/"$preinst" ]; then
222 (set -x; chroot "$rootfs" "$preinst" install)
223 fi
224 ;;
225 Conf)
226 export DPKG_MAINTSCRIPT_NAME=postinst
227 postinst=/var/lib/dpkg/info/${package}${multiarch}.postinst
228 if [ -x "$rootfs"/"$postinst" ]; then
229 (set -x; chroot "$rootfs" "$postinst" configure)
230 fi
231 ;;
232 Remv) ;;
233 *) die "impossible" ;;
234 esac
235 done
236}
237
238apt_extract()
239{
240 apt_get -d -yqq install "$@"
241 actions=$(mktemp) || die 'mktemp failed'
242 apt_get -s -yqq install "$@" | tee "$actions" | dpkg_extract_from_apt
243
244 if [ "$EXTRACT_DPKG_INFO" ]; then
245 install_etc_passwd
246 dpkg_inst_from_apt < "$actions"
247 fi
248
249 rm "$actions"
250}
251
252dpkg_extract_from_apt()
253{
254 while read line; do
255
256 parse_apt_noact_line "$line" || die "unexpected output from apt-get: $line"
257
258 deb=/var/cache/apt/archives/${package}_${version//:/%3a}_${arch}.deb
259 [ -f "$deb" ] || {
260 echo "line=$line" >&2
261 printf '%s\n' "$action" "$package" "$version" "$arch" >&2
262 die "unexpected output from apt-get"
263 }
264 case "$action" in
265 Inst)
266 printf 'Extracting %s\n' "${deb##*/}" >&2
267
268 if [ "$EXTRACT_DPKG_INFO" ]; then
269 is_multiarch_same "$package" && multiarch=":$arch" || multiarch=
270 dpkg_extract_with_info "$deb" "$rootfs" "$multiarch"
271 else
272 dpkg-deb --extract "$deb" "$rootfs"
273 fi
274 ;;
275 Conf) ;;
276 Remv) ;;
277 *) die "impossible" ;;
278 esac
279 done
280}
281
282install_etc_passwd()
283{
284 [ -e "$rootfs"/etc/passwd ] || cp "$rootfs"/usr/share/base-passwd/passwd.master "$rootfs"/etc/passwd
285 [ -e "$rootfs"/etc/group ] || cp "$rootfs"/usr/share/base-passwd/group.master "$rootfs"/etc/group
286}
287
288main_packages_file()
289{
290 local uri_part="${debian_mirror#*/}"
291 while [ "${uri_part:0:1}" = / ]; do
292 uri_part=${uri_part#?}
293 done
294 uri_part=${uri_part//\//_}
295 printf '/var/lib/apt/lists/%s_dists_%s_main_binary-%s_Packages\n' "$uri_part" "$release" "$rootfs_arch"
296}
297
298required_packages()
299{
300 perl -00 -ne \
301 '/^Priority: required/m || next; /^Package: (.*)$/m && print "$1\n"' \
302 "$(main_packages_file)" | sort -u
303}
304
305declare -A is_multiarch_same
306multicheck()
307{
308 local m
309 if [ ! "$multichecked" ]; then
310 for m in $(multiarch_same_packages); do
311 is_multiarch_same[$m]=y
312 done
313 fi
314 multichecked=y
315}
316
317multiarch_same_packages()
318{
319 perl -00 -ne \
320 '/^Multi-Arch: same/mi || next; /^Package: (.*)$/m && print "$1\n"' \
321 "$(main_packages_file)" | sort -u
322}
323
324is_multiarch_same()
325{
326 multicheck
327 [ "${is_multiarch_same[$1]}" ]
328}
329
330set -e
331
332rootfs_arch=$(dpkg-architecture -q DEB_HOST_ARCH) || die 'dpkg-architecture failed'
333release=$(current_debian_codename) && [ "$release" ] || die 'could not determine Debian release name'
334
335if [ "$FAST_MODE" ]; then
336 SKIP_INSTALL=y
337 EXTRACT_DPKG_INFO=y
338 RUN_INST_SCRIPTS=y
339fi
340
341# Set things up so apt-get update works.
342sanity_checks
343generate_apt_config
344populate_rootfs
345
346
347# Initial apt-get update will determine what we shall install.
348[ "$SKIP_UPDATE" ] || apt_get update
349packages=$(required_packages) && [ "$packages" ] || die 'failed to determine list of required packages'
350extra_packages='apt debian-archive-keyring locales'
351
352
353# Some files need to be present before 'apt-get install' can install anything.
354# In particular:
355#
356# 1. binaries used by dpkg 'inst' scripts.
357# 2. /etc/passwd and /etc/group so that 'chown' works
358
359# Rather than fuss about (1), extract everything from all packages.
360
361# Note: apt_extract() runs preinst and postinst scripts itself when
362# $EXTRACT_DPKG_INFO is true.
363apt_extract $packages
364
365if [ "$SKIP_INSTALL" ]; then
366 apt_extract $extra_packages
367else
368 export LC_ALL=C
369 export DEBIAN_FRONTEND=noninteractive
370
371 install_etc_passwd # This handles (2).
372 if [ "$FIX_BROKEN" ]; then
373 dpkg --root="$rootfs" --configure -a
374 fi
375 apt_get install -y $packages $extra_packages
376fi