diff options
Diffstat (limited to 'src/selfstrap')
-rwxr-xr-x | src/selfstrap | 376 |
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 | ||
2 | debian_mirror=http://httpredir.debian.org/debian | ||
3 | |||
4 | if [ $# = 1 ]; then | ||
5 | rootfs=$1 | ||
6 | else | ||
7 | echo "Usage: $0 <target directory>" >&2 | ||
8 | exit 1 | ||
9 | fi | ||
10 | |||
11 | die() { printf 'Error: %s\n' "$*"; exit 1; } | ||
12 | |||
13 | am_root() { [ "$(id -u)" = 0 ]; } | ||
14 | |||
15 | current_debian_codename() | ||
16 | { | ||
17 | # lsb_release -cs | ||
18 | sed -ne 's/^VERSION=.* (\(.*\)).*/\1/p' /etc/os-release | ||
19 | } | ||
20 | |||
21 | sanity_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 | |||
29 | generate_apt_config() | ||
30 | { | ||
31 | APT_CONFIG=$(mktemp) || exit | ||
32 | chmod 644 "$APT_CONFIG" | ||
33 | cat > "$APT_CONFIG" <<EOF | ||
34 | Dir::Etc "${rootfs}/etc/apt/"; | ||
35 | Dir::Etc::Parts "${rootfs}/etc/apt/apt.conf.d/"; | ||
36 | Dir::Etc::PreferencesParts "${rootfs}/etc/apt/preferences.d/"; | ||
37 | EOF | ||
38 | export APT_CONFIG | ||
39 | } | ||
40 | |||
41 | apt_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 | |||
85 | idem() { if [ ! -e "${!#}" ]; then "$@"; fi; } | ||
86 | idem_mknod() { if [ ! -e "$3" ]; then mknod "$@"; fi; } | ||
87 | |||
88 | # This function is copied (with modifications) from debootstrap. | ||
89 | install_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 | |||
115 | mount_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 | |||
125 | umount_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 | |||
134 | write_lines_once() | ||
135 | { | ||
136 | local output="$1" | ||
137 | shift | ||
138 | [ -e "$output" ] || printf '%s\n' "$@" > "$output" | ||
139 | } | ||
140 | |||
141 | populate_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 | |||
159 | parse_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 | |||
181 | dpkg_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' | ||
189 | if [ "$TAR_FILENAME" = ./control ]; then | ||
190 | (sed "/^Package:/a Status: install ok installed"; echo) >> "$TARGET"/var/lib/dpkg/status | ||
191 | else | ||
192 | f=$TARGET/var/lib/dpkg/info/$PKG.${TAR_FILENAME#./} | ||
193 | cat > "$f" | ||
194 | chmod $TAR_MODE "$f" | ||
195 | fi | ||
196 | EOF | ||
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 | |||
204 | apt_run_inst() | ||
205 | { | ||
206 | apt_get -s -yqq install "$@" | dpkg_inst_from_apt | ||
207 | } | ||
208 | |||
209 | dpkg_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 | |||
238 | apt_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 | |||
252 | dpkg_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 | |||
282 | install_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 | |||
288 | main_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 | |||
298 | required_packages() | ||
299 | { | ||
300 | perl -00 -ne \ | ||
301 | '/^Priority: required/m || next; /^Package: (.*)$/m && print "$1\n"' \ | ||
302 | "$(main_packages_file)" | sort -u | ||
303 | } | ||
304 | |||
305 | declare -A is_multiarch_same | ||
306 | multicheck() | ||
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 | |||
317 | multiarch_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 | |||
324 | is_multiarch_same() | ||
325 | { | ||
326 | multicheck | ||
327 | [ "${is_multiarch_same[$1]}" ] | ||
328 | } | ||
329 | |||
330 | set -e | ||
331 | |||
332 | rootfs_arch=$(dpkg-architecture -q DEB_HOST_ARCH) || die 'dpkg-architecture failed' | ||
333 | release=$(current_debian_codename) && [ "$release" ] || die 'could not determine Debian release name' | ||
334 | |||
335 | if [ "$FAST_MODE" ]; then | ||
336 | SKIP_INSTALL=y | ||
337 | EXTRACT_DPKG_INFO=y | ||
338 | RUN_INST_SCRIPTS=y | ||
339 | fi | ||
340 | |||
341 | # Set things up so apt-get update works. | ||
342 | sanity_checks | ||
343 | generate_apt_config | ||
344 | populate_rootfs | ||
345 | |||
346 | |||
347 | # Initial apt-get update will determine what we shall install. | ||
348 | [ "$SKIP_UPDATE" ] || apt_get update | ||
349 | packages=$(required_packages) && [ "$packages" ] || die 'failed to determine list of required packages' | ||
350 | extra_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. | ||
363 | apt_extract $packages | ||
364 | |||
365 | if [ "$SKIP_INSTALL" ]; then | ||
366 | apt_extract $extra_packages | ||
367 | else | ||
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 | ||
376 | fi | ||