diff options
author | Andrew Cady <d@jerkface.net> | 2017-08-30 19:20:21 -0400 |
---|---|---|
committer | Andrew Cady <d@jerkface.net> | 2017-08-30 19:20:21 -0400 |
commit | 7392932128db786fe915e8ef432953fbf55bc0f2 (patch) | |
tree | 0a3c2908dfa62d2a67ae748e85b011ad6c37c250 /multistrap/selfstrap | |
parent | d2e4d3fb467b332fe0f972ccab2c5c37ccdd5fd5 (diff) |
selfstrap: debootstrap using local /var/cache/apt/archives
Diffstat (limited to 'multistrap/selfstrap')
-rwxr-xr-x | multistrap/selfstrap | 302 |
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 | ||
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 | 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 | |||
81 | write_lines_once() | ||
82 | { | ||
83 | local output="$1" | ||
84 | shift | ||
85 | [ -e "$output" ] || printf '%s\n' "$@" > "$output" | ||
86 | } | ||
87 | |||
88 | populate_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 | |||
105 | parse_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 | |||
127 | extract_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 | ||
136 | f=$TARGET/var/lib/dpkg/info/$PKG.${TAR_FILENAME#./} | ||
137 | cat > "$f" | ||
138 | chmod $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 | |||
146 | apt_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 | |||
177 | apt_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 | |||
207 | install_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 | |||
213 | main_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 | |||
223 | required_packages() | ||
224 | { | ||
225 | perl -00 -ne \ | ||
226 | '/^Priority: required/m || next; /^Package: (.*)$/m && print "$1\n"' \ | ||
227 | "$(main_packages_file)" | sort -u | ||
228 | } | ||
229 | |||
230 | multiarch_same_packages() | ||
231 | { | ||
232 | [ "$multiarch_same_packages" ] || multiarch_same_packages=$(multiarch_same_packages_) | ||
233 | echo "$multiarch_same_packages" | ||
234 | } | ||
235 | |||
236 | multiarch_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 | |||
243 | is_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 | |||
252 | set -e | ||
253 | |||
254 | rootfs_arch=$(dpkg-architecture -q DEB_HOST_ARCH) || die 'dpkg-architecture failed' | ||
255 | release=$(current_debian_codename) && [ "$release" ] || die 'could not determine Debian release name' | ||
256 | |||
257 | if [ "$FAST_MODE" ]; then | ||
258 | SKIP_INSTALL=y | ||
259 | EXTRACT_DPKG_INFO=y | ||
260 | RUN_INST_SCRIPTS=y | ||
261 | fi | ||
262 | |||
263 | # Set things up so apt-get update works. | ||
264 | sanity_checks | ||
265 | generate_apt_config | ||
266 | populate_rootfs | ||
267 | |||
268 | |||
269 | # Initial apt-get update will determine what we shall install. | ||
270 | [ "$SKIP_UPDATE" ] || apt_get update | ||
271 | packages=$(required_packages) && [ "$packages" ] || die 'failed to determine list of required packages' | ||
272 | extra_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. | ||
284 | apt_extract $packages | ||
285 | # This handles (2). | ||
286 | install_etc_passwd | ||
287 | |||
288 | |||
289 | # Finally we are ready to run apt-get install. | ||
290 | export LC_ALL=C | ||
291 | export DEBIAN_FRONTEND=noninteractive | ||
292 | if [ "$SKIP_INSTALL" ]; then | ||
293 | apt_extract $extra_packages | ||
294 | if [ "$RUN_INST_SCRIPTS" ]; then | ||
295 | apt_run_inst $packages $extra_packages | ||
296 | fi | ||
297 | else | ||
298 | if [ "$FIX_BROKEN" ]; then | ||
299 | dpkg --root="$rootfs" --configure -a | ||
300 | fi | ||
301 | apt_get install -y $packages $extra_packages | ||
302 | fi | ||