diff options
-rwxr-xr-x | debootstrap.sh | 207 |
1 files changed, 207 insertions, 0 deletions
diff --git a/debootstrap.sh b/debootstrap.sh new file mode 100755 index 0000000..e0bbc4b --- /dev/null +++ b/debootstrap.sh | |||
@@ -0,0 +1,207 @@ | |||
1 | #!/bin/sh | ||
2 | imgdir=./debootstrap | ||
3 | warn() { printf 'Warning: %s\n' "$*" >&2; } | ||
4 | die() { printf 'Error: %s\n' "$*" >&2; exit 1; } | ||
5 | [ -d "$imgdir" ] || die "directory does not exist: $imgdir" | ||
6 | |||
7 | usage() | ||
8 | { | ||
9 | cat <<EOF >&2 | ||
10 | Usage: | ||
11 | $0 init <suite> | ||
12 | $0 new <suite> <new-name> | ||
13 | $0 clone <suite> <source-name> <new-name> | ||
14 | $0 chroot <suite> <name> | ||
15 | $0 list | ||
16 | |||
17 | EOF | ||
18 | list | ||
19 | exit 1 | ||
20 | } | ||
21 | |||
22 | list() | ||
23 | { | ||
24 | local f suite name header_printed= | ||
25 | for f in $imgdir/*.btrfs; do | ||
26 | [ -e "$f" ] || continue | ||
27 | f=${f##*/} | ||
28 | f=${f%.btrfs} | ||
29 | case "$f" in *.*) continue ;; esac | ||
30 | suite=${f%%-*} | ||
31 | [ "$header_printed" ] || echo 'Existing initialized suites:' | ||
32 | printf ' %s\n' "$suite" | ||
33 | header_printed=y | ||
34 | done | ||
35 | header_printed= | ||
36 | for f in $imgdir/*.*.btrfs; do | ||
37 | [ -e "$f" ] || continue | ||
38 | f=${f##*/} | ||
39 | f=${f%.btrfs} | ||
40 | suite=${f%%-*} | ||
41 | name=${f##*.} | ||
42 | [ "$header_printed" ] || echo 'Existing images:' | ||
43 | printf ' %s %s\n' "$suite" "$name" | ||
44 | header_printed=y | ||
45 | done | ||
46 | } | ||
47 | |||
48 | validate_suite() | ||
49 | { | ||
50 | case "$1" in | ||
51 | jessie|stretch|sid) return 0 ;; | ||
52 | *) return 1 ;; | ||
53 | esac | ||
54 | } | ||
55 | |||
56 | i_am_root() { [ "$(id -u)" = 0 ]; } | ||
57 | |||
58 | btrfs_show_subvolume_id() | ||
59 | { | ||
60 | local result path="$1" | ||
61 | result=$(btrfs subvolume show "$path" | sed -n -e 's/^[ \t]*Subvolume ID:[ \t]*//p; s/.*is toplevel subvolume/5/p') | ||
62 | if [ "$result" ] | ||
63 | then printf '%s\n' "$result" | ||
64 | else false | ||
65 | fi | ||
66 | } | ||
67 | |||
68 | mkfs_btrfs() | ||
69 | { | ||
70 | local target="$1" | ||
71 | mountpoint="$2" # can't be local because of trap | ||
72 | |||
73 | mkfs.btrfs "$target" || die "mkfs.btrfs failed" | ||
74 | mount -t btrfs "$target" "$mountpoint" || die "mount failed" | ||
75 | trap 'umount "$mountpoint"' EXIT | ||
76 | |||
77 | btrfs subvolume create "$mountpoint"/root || die "command 'btrfs subvolume create' failed" | ||
78 | mkdir -p "$mountpoint"/root/var/cache/apt | ||
79 | btrfs subvolume create "$mountpoint"/root/var/cache/apt/archives || die "command 'btrfs subvolume create' failed" | ||
80 | subvol_id=$(btrfs_show_subvolume_id "$mountpoint"/root) || die "could not find btrfs subvolume name" | ||
81 | btrfs subvolume set-default "$subvol_id" "$mountpoint" || die "command 'btrfs subvolume set-default' failed" | ||
82 | |||
83 | trap - EXIT | ||
84 | umount "$mountpoint" | ||
85 | } | ||
86 | |||
87 | suite_to_basename() | ||
88 | { | ||
89 | suite=$1 | ||
90 | variant=minbase | ||
91 | arch=$(dpkg-architecture -q DEB_HOST_ARCH) || die "command 'dpkg-architecture' failed" | ||
92 | |||
93 | printf '%s\n' "$suite-$variant-$arch.btrfs" | ||
94 | } | ||
95 | |||
96 | suite_name_to_imagename() | ||
97 | { | ||
98 | suite=$1 | ||
99 | name=$2 | ||
100 | variant=minbase | ||
101 | arch=$(dpkg-architecture -q DEB_HOST_ARCH) || die "command 'dpkg-architecture' failed" | ||
102 | |||
103 | printf '%s/%s-%s-%s.%s.btrfs\n' "$imgdir" "$suite" "$variant" "$arch" "$name" | ||
104 | } | ||
105 | |||
106 | chroot_image() | ||
107 | { | ||
108 | suite=$1 | ||
109 | name=$2 | ||
110 | [ "$suite" -a "$name" ] || usage | ||
111 | imagename=$(suite_name_to_imagename "$suite" "$name") | ||
112 | [ -e "$imagename" ] || die "no such file: $imagename" | ||
113 | [ -d "$imagename".mnt ] || mkdir "$imagename".mnt || die "mkdir" | ||
114 | mountpoint -q "$imagename".mnt || mount "$imagename" "$imagename".mnt || die "mount" | ||
115 | |||
116 | unshare -f -m -p \ | ||
117 | chroot "$imagename".mnt \ | ||
118 | /bin/sh -c 'mount -t proc proc /proc; mount -t devpts devpts /dev/pts; exec /bin/bash' | ||
119 | |||
120 | r=$? | ||
121 | umount "$imagename".mnt | ||
122 | rmdir "$imagename".mnt | ||
123 | return $r | ||
124 | } | ||
125 | |||
126 | clone() | ||
127 | { | ||
128 | suite=$1 | ||
129 | source_name=$2 | ||
130 | name=$3 | ||
131 | [ "$suite" -a "$name" -a "$source_name" ] || usage | ||
132 | image_basename=$imgdir/$(suite_to_basename "$suite") || die 'suite_to_basename' | ||
133 | |||
134 | source=${image_basename%.btrfs}.$source_name.btrfs | ||
135 | dest=${image_basename%.btrfs}.$name.btrfs | ||
136 | |||
137 | [ -e "$source" ] || die "source image not found -- run '$0 new $suite $source_name' to create it" | ||
138 | [ ! -e "$dest" ] || die "file exists: $dest" | ||
139 | cp --reflink=always "$source" "$dest" | ||
140 | } | ||
141 | |||
142 | new() | ||
143 | { | ||
144 | suite=$1 | ||
145 | name=$2 | ||
146 | [ "$suite" -a "$name" ] || usage | ||
147 | image_basename=$imgdir/$(suite_to_basename "$suite") || die 'suite_to_basename' | ||
148 | |||
149 | source=$image_basename | ||
150 | dest=${source%.btrfs}.$name.btrfs | ||
151 | |||
152 | [ -e "$source" ] || die "source image not found -- run '$0 init $suite' to create it" | ||
153 | [ ! -e "$dest" ] || die "file exists: $dest" | ||
154 | cp --reflink=always "$source" "$dest" | ||
155 | } | ||
156 | |||
157 | init() | ||
158 | { | ||
159 | suite=$1 | ||
160 | variant=minbase | ||
161 | arch=$(dpkg-architecture -q DEB_HOST_ARCH) || die "command 'dpkg-architecture' failed" | ||
162 | |||
163 | size=1G # truncate(1) format | ||
164 | |||
165 | i_am_root || die 'you are not root' | ||
166 | |||
167 | validate_suite "$suite" || die "invalid suite: '$suite'" | ||
168 | |||
169 | target=$imgdir/$suite-$variant-$arch.btrfs | ||
170 | [ -e "$target" ] && return | ||
171 | [ -e "$target".tmp ] && die "refusing to overwrite existing temporary file: $target.tmp" | ||
172 | |||
173 | [ -d "$target".mnt ] || mkdir "$target".mnt || die "could not create directory $target.mnt" | ||
174 | |||
175 | truncate -s "$size" "$target".tmp || die "truncate failed" | ||
176 | mkfs_btrfs "$target".tmp "$target".mnt || die "btrfs filesystem creation failed" | ||
177 | mount -t btrfs "$target".tmp "$target".mnt || die "mount failed" | ||
178 | trap 'umount "$target.mnt"' EXIT | ||
179 | |||
180 | debootstrap_efficiently "$arch" "$variant" "$suite" \ | ||
181 | "$target".mnt "${target%.btrfs}".debs.txt || die "debootstrap failed" | ||
182 | |||
183 | if umount "$target".mnt | ||
184 | then trap - EXIT | ||
185 | else warn "umount failed" | ||
186 | fi | ||
187 | |||
188 | mv "$target".tmp "$target" | ||
189 | } | ||
190 | |||
191 | debootstrap_efficiently() | ||
192 | { | ||
193 | local arch="$1" variant="$2" suite="$3" target="$4" savedebs="$5" | ||
194 | set -- --arch "$arch" --variant "$variant" "$suite" "$target" | ||
195 | debs=$(debootstrap --print-debs --keep-debootstrap-dir "$@" | tee "$savedebs") || die "debootstrap failed" | ||
196 | for deb in $debs; do | ||
197 | cp /var/cache/apt/archives/${deb}_* $target/var/cache/apt/archives/ | ||
198 | done | ||
199 | |||
200 | debootstrap "$@" || die "debootstrap failed" | ||
201 | } | ||
202 | |||
203 | case "$1" in | ||
204 | init|new|clone|list) cmd=$1; shift; $cmd "$@" ;; | ||
205 | chroot) cmd=chroot_image; shift; $cmd "$@" ;; | ||
206 | *) usage ;; | ||
207 | esac | ||