]>
Commit | Line | Data |
---|---|---|
d1458ac8 SH |
1 | #!/bin/bash |
2 | ||
65d8ae9c SM |
3 | # template script for generating ubuntu container for LXC based on released |
4 | # cloud images. | |
d1458ac8 SH |
5 | # |
6 | # Copyright © 2012 Serge Hallyn <serge.hallyn@canonical.com> | |
7 | # | |
acbb59f5 SH |
8 | # This library is free software; you can redistribute it and/or |
9 | # modify it under the terms of the GNU Lesser General Public | |
10 | # License as published by the Free Software Foundation; either | |
11 | # version 2.1 of the License, or (at your option) any later version. | |
d1458ac8 | 12 | |
acbb59f5 SH |
13 | # This library is distributed in the hope that it will be useful, |
14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
16 | # Lesser General Public License for more details. | |
d1458ac8 | 17 | |
acbb59f5 SH |
18 | # You should have received a copy of the GNU Lesser General Public |
19 | # License along with this library; if not, write to the Free Software | |
20 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | |
d1458ac8 SH |
21 | |
22 | set -e | |
23 | ||
65d8ae9c SM |
24 | STATE_DIR="@LOCALSTATEDIR@" |
25 | HOOK_DIR="@LXCHOOKDIR@" | |
26 | CLONE_HOOK_FN="$HOOK_DIR/ubuntu-cloud-prep" | |
f2a95ee1 | 27 | LXC_TEMPLATE_CONFIG="@LXCTEMPLATECONFIG@" |
ad3f14ab SM |
28 | KNOWN_RELEASES="lucid precise quantal saucy trusty" |
29 | skip_arch_check=${UCTEMPLATE_SKIP_ARCH_CHECK:-0} | |
65d8ae9c | 30 | |
d1458ac8 SH |
31 | if [ -r /etc/default/lxc ]; then |
32 | . /etc/default/lxc | |
33 | fi | |
34 | ||
1aad9e44 SH |
35 | am_in_userns() { |
36 | [ -e /proc/self/uid_map ] || { echo no; return; } | |
37 | [ "$(wc -l /proc/self/uid_map | awk '{ print $1 }')" -eq 1 ] || { echo yes; return; } | |
38 | line=$(awk '{ print $1 " " $2 " " $3 }' /proc/self/uid_map) | |
39 | [ "$line" = "0 0 4294967295" ] && { echo no; return; } | |
40 | echo yes | |
41 | } | |
42 | ||
43 | in_userns=0 | |
44 | [ $(am_in_userns) = "yes" ] && in_userns=1 | |
1aad9e44 | 45 | |
d1458ac8 SH |
46 | copy_configuration() |
47 | { | |
48 | path=$1 | |
49 | rootfs=$2 | |
50 | name=$3 | |
51 | arch=$4 | |
42ff5f0f | 52 | release=$5 |
d1458ac8 SH |
53 | |
54 | if [ $arch = "i386" ]; then | |
55 | arch="i686" | |
56 | fi | |
57 | ||
4759162d SH |
58 | # if there is exactly one veth network entry, make sure it has an |
59 | # associated hwaddr. | |
60 | nics=`grep -e '^lxc\.network\.type[ \t]*=[ \t]*veth' $path/config | wc -l` | |
61 | if [ $nics -eq 1 ]; then | |
daaf41b3 | 62 | grep -q "^lxc.network.hwaddr" $path/config || sed -i -e "/^lxc\.network\.type[ \t]*=[ \t]*veth/a lxc.network.hwaddr = 00:16:3e:$(openssl rand -hex 3| sed 's/\(..\)/\1:/g; s/.$//')" $path/config |
4759162d SH |
63 | fi |
64 | ||
f2a95ee1 SG |
65 | # Generate the configuration file |
66 | ## Create the fstab (empty by default) | |
67 | touch $path/fstab | |
d1458ac8 | 68 | |
f2a95ee1 SG |
69 | ## Relocate all the network config entries |
70 | sed -i -e "/lxc.network/{w ${path}/config-network" -e "d}" $path/config | |
9313e1e6 | 71 | |
f2a95ee1 SG |
72 | ## Relocate any other config entries |
73 | sed -i -e "/lxc./{w ${path}/config-auto" -e "d}" $path/config | |
f02ce27d | 74 | |
f2a95ee1 SG |
75 | ## Add all the includes |
76 | echo "" >> $path/config | |
77 | echo "# Common configuration" >> $path/config | |
78 | if [ -e "${LXC_TEMPLATE_CONFIG}/ubuntu-cloud.common.conf" ]; then | |
79 | echo "lxc.include = ${LXC_TEMPLATE_CONFIG}/ubuntu-cloud.common.conf" >> $path/config | |
80 | fi | |
81 | if [ -e "${LXC_TEMPLATE_CONFIG}/ubuntu-cloud.${release}.conf" ]; then | |
82 | echo "lxc.include = ${LXC_TEMPLATE_CONFIG}/ubuntu-cloud.${release}.conf" >> $path/config | |
83 | fi | |
84 | if [ $in_userns -eq 1 ] && [ -e "${LXC_TEMPLATE_CONFIG}/ubuntu-cloud.userns.conf" ]; then | |
85 | echo "lxc.include = ${LXC_TEMPLATE_CONFIG}/ubuntu-cloud.userns.conf" >> $path/config | |
57d116ab | 86 | fi |
d1458ac8 | 87 | |
f2a95ee1 SG |
88 | ## Add the container-specific config |
89 | echo "" >> $path/config | |
90 | echo "# Container specific configuration" >> $path/config | |
91 | [ -e "$path/config-auto" ] && cat $path/config-auto >> $path/config && rm $path/config-auto | |
92 | grep -q "^lxc.rootfs" $path/config 2>/dev/null || echo "lxc.rootfs = $rootfs" >> $path/config | |
93 | cat <<EOF >> $path/config | |
94 | lxc.mount = $path/fstab | |
95 | lxc.utsname = $name | |
96 | lxc.arch = $arch | |
bf7d76cf | 97 | EOF |
d1458ac8 | 98 | |
f2a95ee1 SG |
99 | ## Re-add the previously removed network config |
100 | echo "" >> $path/config | |
101 | echo "# Network configuration" >> $path/config | |
102 | cat $path/config-network >> $path/config | |
103 | rm $path/config-network | |
1aad9e44 | 104 | |
0a3673e8 SG |
105 | # Set initial timezone as on host |
106 | if [ -f /etc/timezone ]; then | |
107 | cat /etc/timezone > $rootfs/etc/timezone | |
108 | chroot $rootfs dpkg-reconfigure -f noninteractive tzdata | |
109 | elif [ -f /etc/sysconfig/clock ]; then | |
17abf278 | 110 | . /etc/sysconfig/clock |
0a3673e8 SG |
111 | echo $ZONE > $rootfs/etc/timezone |
112 | chroot $rootfs dpkg-reconfigure -f noninteractive tzdata | |
113 | else | |
114 | echo "Timezone in container is not configured. Adjust it manually." | |
115 | fi | |
116 | ||
542939c3 | 117 | # rmdir /dev/shm for containers that have /run/shm |
42ff5f0f SH |
118 | # I'm afraid of doing rm -rf $rootfs/dev/shm, in case it did |
119 | # get bind mounted to the host's /run/shm. So try to rmdir | |
120 | # it, and in case that fails move it out of the way. | |
5ff33774 | 121 | # NOTE: This can only be removed once 12.04 goes out of support |
542939c3 | 122 | if [ ! -L $rootfs/dev/shm ] && [ -d $rootfs/run/shm ] && [ -e $rootfs/dev/shm ]; then |
5ff33774 | 123 | rmdir $rootfs/dev/shm 2>/dev/null || mv $rootfs/dev/shm $rootfs/dev/shm.bak |
42ff5f0f SH |
124 | ln -s /run/shm $rootfs/dev/shm |
125 | fi | |
126 | ||
d1458ac8 SH |
127 | return 0 |
128 | } | |
129 | ||
130 | usage() | |
131 | { | |
132 | cat <<EOF | |
4759162d SH |
133 | LXC Container configuration for Ubuntu Cloud images. |
134 | ||
135 | Generic Options | |
136 | [ -r | --release <release> ]: Release name of container, defaults to host | |
1897e3bc | 137 | [ --rootfs <path> ]: Path in which rootfs will be placed |
3f5f5d99 | 138 | [ -a | --arch ]: Architecture of container, defaults to host architecture |
4759162d | 139 | [ -T | --tarball ]: Location of tarball |
52c8f624 | 140 | [ -d | --debug ]: Run with 'set -x' to debug errors |
ad3f14ab | 141 | [ -s | --stream]: Use specified stream rather than 'tryreleased' |
4759162d | 142 | |
65d8ae9c SM |
143 | Additionally, clone hooks can be passed through (ie, --userdata). For those, |
144 | see: | |
145 | $CLONE_HOOK_FN --help | |
d1458ac8 SH |
146 | EOF |
147 | return 0 | |
148 | } | |
149 | ||
2133f58c | 150 | options=$(getopt -o a:hp:r:n:Fi:CLS:T:ds:u: -l arch:,help,rootfs:,path:,release:,name:,flush-cache,hostid:,auth-key:,cloud,no_locales,tarball:,debug,stream:,userdata:,mapped-uid:,mapped-gid: -- "$@") |
d1458ac8 SH |
151 | if [ $? -ne 0 ]; then |
152 | usage $(basename $0) | |
153 | exit 1 | |
154 | fi | |
155 | eval set -- "$options" | |
156 | ||
57d116ab | 157 | mapped_uid=-1 |
2133f58c | 158 | mapped_gid=-1 |
65d8ae9c SM |
159 | # default release is precise, or the systems release if recognized |
160 | release=precise | |
d1458ac8 SH |
161 | if [ -f /etc/lsb-release ]; then |
162 | . /etc/lsb-release | |
65d8ae9c | 163 | rels=$(ubuntu-distro-info --supported 2>/dev/null) || |
ad3f14ab | 164 | rels="$KNOWN_RELEASES" |
65d8ae9c SM |
165 | for r in $rels; do |
166 | [ "$DISTRIB_CODENAME" = "$r" ] && release="$r" | |
167 | done | |
d1458ac8 SH |
168 | fi |
169 | ||
d1458ac8 SH |
170 | # Code taken from debootstrap |
171 | if [ -x /usr/bin/dpkg ] && /usr/bin/dpkg --print-architecture >/dev/null 2>&1; then | |
172 | arch=`/usr/bin/dpkg --print-architecture` | |
173 | elif type udpkg >/dev/null 2>&1 && udpkg --print-architecture >/dev/null 2>&1; then | |
174 | arch=`/usr/bin/udpkg --print-architecture` | |
175 | else | |
ed4616b1 | 176 | arch=$(uname -m) |
d1458ac8 SH |
177 | if [ "$arch" = "i686" ]; then |
178 | arch="i386" | |
179 | elif [ "$arch" = "x86_64" ]; then | |
180 | arch="amd64" | |
181 | elif [ "$arch" = "armv7l" ]; then | |
3eecde70 | 182 | # note: arm images don't exist before oneiric; are called armhf in |
b8bced69 | 183 | # precise and later; and are not supported by the query, so we don't actually |
3eecde70 SH |
184 | # support them yet (see check later on). When Query2 is available, |
185 | # we'll use that to enable arm images. | |
8a63c0a9 | 186 | arch="armhf" |
8a3c76b2 SG |
187 | elif [ "$arch" = "aarch64" ]; then |
188 | arch="arm64" | |
189 | elif [ "$arch" = "ppc64le" ]; then | |
190 | arch="ppc64el" | |
d1458ac8 SH |
191 | fi |
192 | fi | |
193 | ||
52c8f624 | 194 | debug=0 |
d1458ac8 SH |
195 | hostarch=$arch |
196 | cloud=0 | |
4759162d | 197 | locales=1 |
d1458ac8 | 198 | flushcache=0 |
4b954f12 | 199 | stream="tryreleased" |
65d8ae9c | 200 | cloneargs=() |
d1458ac8 SH |
201 | while true |
202 | do | |
203 | case "$1" in | |
4759162d SH |
204 | -h|--help) usage $0 && exit 0;; |
205 | -p|--path) path=$2; shift 2;; | |
206 | -n|--name) name=$2; shift 2;; | |
207 | -F|--flush-cache) flushcache=1; shift 1;; | |
208 | -r|--release) release=$2; shift 2;; | |
209 | -a|--arch) arch=$2; shift 2;; | |
4759162d | 210 | -T|--tarball) tarball=$2; shift 2;; |
52c8f624 | 211 | -d|--debug) debug=1; shift 1;; |
427bffc7 | 212 | -s|--stream) stream=$2; shift 2;; |
65d8ae9c SM |
213 | --rootfs) rootfs=$2; shift 2;; |
214 | -L|--no?locales) cloneargs[${#cloneargs[@]}]="--no-locales"; shift 1;; | |
215 | -i|--hostid) cloneargs[${#cloneargs[@]}]="--hostid=$2"; shift 2;; | |
216 | -u|--userdata) cloneargs[${#cloneargs[@]}]="--userdata=$2"; shift 2;; | |
217 | -C|--cloud) cloneargs[${#cloneargs[@]}]="--cloud"; shift 1;; | |
218 | -S|--auth-key) cloneargs[${#cloneargs[@]}]="--auth-key=$2"; shift 2;; | |
57d116ab | 219 | --mapped-uid) mapped_uid=$2; shift 2;; |
2133f58c | 220 | --mapped-gid) mapped_gid=$2; shift 2;; |
4759162d | 221 | --) shift 1; break ;; |
d1458ac8 SH |
222 | *) break ;; |
223 | esac | |
224 | done | |
225 | ||
65d8ae9c SM |
226 | cloneargs=( "--name=$name" "${cloneargs[@]}" ) |
227 | ||
3eecde70 | 228 | if [ $debug -eq 1 ]; then |
52c8f624 SH |
229 | set -x |
230 | fi | |
231 | ||
17abf278 | 232 | if [ "$arch" = "i686" ]; then |
d1458ac8 SH |
233 | arch=i386 |
234 | fi | |
235 | ||
ad3f14ab SM |
236 | if [ "$skip_arch_check" = "0" ]; then |
237 | case "$hostarch:$arch" in | |
238 | $arch:$arch) : ;; # the host == container | |
d4641754 SG |
239 | amd64:i386) :;; # supported "cross" |
240 | arm64:arm*) :;; # supported "cross" | |
241 | armel:armhf) :;; # supported "cross" | |
242 | armhf:armel) :;; # supported "cross" | |
ad3f14ab SM |
243 | *) echo "cannot create '$arch' container on hostarch '$hostarch'"; |
244 | exit 1;; | |
245 | esac | |
d1458ac8 SH |
246 | fi |
247 | ||
4b954f12 DJL |
248 | if [ "$stream" != "daily" -a "$stream" != "released" -a "$stream" != "tryreleased" ]; then |
249 | echo "Only 'daily' and 'released' and 'tryreleased' streams are supported" | |
427bffc7 SH |
250 | exit 1 |
251 | fi | |
252 | ||
d1458ac8 SH |
253 | if [ -z "$path" ]; then |
254 | echo "'path' parameter is required" | |
255 | exit 1 | |
256 | fi | |
257 | ||
258 | if [ "$(id -u)" != "0" ]; then | |
259 | echo "This script should be run as 'root'" | |
260 | exit 1 | |
261 | fi | |
262 | ||
1881820a SH |
263 | # detect rootfs |
264 | config="$path/config" | |
1897e3bc SH |
265 | if [ -z "$rootfs" ]; then |
266 | if grep -q '^lxc.rootfs' $config 2>/dev/null ; then | |
853d58fd | 267 | rootfs=$(awk -F= '/^lxc.rootfs =/{ print $2 }' $config) |
1897e3bc SH |
268 | else |
269 | rootfs=$path/rootfs | |
270 | fi | |
1881820a | 271 | fi |
d1458ac8 SH |
272 | |
273 | type ubuntu-cloudimg-query | |
274 | type wget | |
275 | ||
276 | # determine the url, tarball, and directory names | |
277 | # download if needed | |
65d8ae9c | 278 | cache="$STATE_DIR/cache/lxc/cloud-$release" |
9cde0368 SG |
279 | if [ $in_userns -eq 1 ]; then |
280 | STATE_DIR="$HOME/.cache/lxc/" | |
281 | cache="$HOME/.cache/lxc/cloud-$release" | |
282 | fi | |
d1458ac8 SH |
283 | |
284 | mkdir -p $cache | |
285 | ||
4b954f12 DJL |
286 | if [ "$stream" = "tryreleased" ]; then |
287 | stream=released | |
288 | ubuntu-cloudimg-query $release $stream $arch 1>/dev/null 2>/dev/null || stream=daily | |
289 | fi | |
290 | ||
4759162d | 291 | if [ -n "$tarball" ]; then |
b942e672 | 292 | url2="$tarball" |
4759162d | 293 | else |
ad3f14ab SM |
294 | if ! url1=`ubuntu-cloudimg-query $release $stream $arch --format "%{url}\n"`; then |
295 | echo "There is no download available for release=$release, stream=$stream, arch=$arch" | |
296 | [ "$stream" = "daily" ] || echo "You may try with '--stream=daily'" | |
297 | exit | |
298 | fi | |
b942e672 | 299 | url2=`echo $url1 | sed -e 's/.tar.gz/-root\0/'` |
4759162d SH |
300 | fi |
301 | ||
d1458ac8 SH |
302 | filename=`basename $url2` |
303 | ||
f1ccde27 SH |
304 | wgetcleanup() |
305 | { | |
b942e672 | 306 | rm -f $filename |
f1ccde27 SH |
307 | } |
308 | ||
3eecde70 SH |
309 | buildcleanup() |
310 | { | |
311 | cd $rootfs | |
312 | umount -l $cache/$xdir || true | |
313 | rm -rf $cache | |
314 | } | |
315 | ||
316 | # if the release doesn't have a *-rootfs.tar.gz, then create one from the | |
317 | # cloudimg.tar.gz by extracting the .img, mounting it loopback, and creating | |
318 | # a tarball from the mounted image. | |
319 | build_root_tgz() | |
320 | { | |
321 | url=$1 | |
322 | filename=$2 | |
323 | ||
324 | xdir=`mktemp -d -p .` | |
325 | tarname=`basename $url` | |
edd3810e | 326 | imgname="$release-*-cloudimg-$arch.img" |
f1ccde27 | 327 | trap buildcleanup EXIT SIGHUP SIGINT SIGTERM |
3eecde70 SH |
328 | if [ $flushcache -eq 1 -o ! -f $cache/$tarname ]; then |
329 | rm -f $tarname | |
330 | echo "Downloading cloud image from $url" | |
331 | wget $url || { echo "Couldn't find cloud image $url."; exit 1; } | |
332 | fi | |
333 | echo "Creating new cached cloud image rootfs" | |
9c3bc32c | 334 | tar --wildcards -zxf "$tarname" "$imgname" |
3eecde70 | 335 | mount -o loop $imgname $xdir |
9c3bc32c | 336 | (cd $xdir; tar --numeric-owner -cpzf "../$filename" .) |
3eecde70 SH |
337 | umount $xdir |
338 | rm -f $tarname $imgname | |
339 | rmdir $xdir | |
340 | echo "New cloud image cache created" | |
341 | trap EXIT | |
f1ccde27 SH |
342 | trap SIGHUP |
343 | trap SIGINT | |
344 | trap SIGTERM | |
3eecde70 SH |
345 | } |
346 | ||
1aad9e44 | 347 | do_extract_rootfs() { |
d1458ac8 SH |
348 | |
349 | cd $cache | |
350 | if [ $flushcache -eq 1 ]; then | |
351 | echo "Clearing the cached images" | |
352 | rm -f $filename | |
353 | fi | |
354 | ||
f1ccde27 | 355 | trap wgetcleanup EXIT SIGHUP SIGINT SIGTERM |
d1458ac8 | 356 | if [ ! -f $filename ]; then |
b942e672 | 357 | wget $url2 || build_root_tgz $url1 $filename |
d1458ac8 | 358 | fi |
f1ccde27 SH |
359 | trap EXIT |
360 | trap SIGHUP | |
361 | trap SIGINT | |
362 | trap SIGTERM | |
d1458ac8 | 363 | |
3eecde70 | 364 | echo "Extracting container rootfs" |
d1458ac8 SH |
365 | mkdir -p $rootfs |
366 | cd $rootfs | |
57d116ab SH |
367 | if [ $in_userns -eq 1 ]; then |
368 | tar --anchored --exclude="dev/*" --numeric-owner -xpzf "$cache/$filename" | |
2b142295 | 369 | mkdir -p $rootfs/dev/pts/ |
57d116ab SH |
370 | else |
371 | tar --numeric-owner -xpzf "$cache/$filename" | |
372 | fi | |
1aad9e44 SH |
373 | } |
374 | ||
375 | if [ -n "$tarball" ]; then | |
376 | do_extract_rootfs | |
377 | else | |
65d8ae9c | 378 | mkdir -p "$STATE_DIR/lock/subsys/" |
1aad9e44 | 379 | ( |
17abf278 | 380 | flock -x 9 |
1aad9e44 | 381 | do_extract_rootfs |
17abf278 | 382 | ) 9>"$STATE_DIR/lock/subsys/lxc-ubuntu-cloud" |
1aad9e44 | 383 | fi |
d1458ac8 | 384 | |
42ff5f0f | 385 | copy_configuration $path $rootfs $name $arch $release |
d1458ac8 | 386 | |
65d8ae9c SM |
387 | "$CLONE_HOOK_FN" "${cloneargs[@]}" "$rootfs" |
388 | ||
57d116ab | 389 | if [ $mapped_uid -ne -1 ]; then |
c01c25fc SG |
390 | chown $mapped_uid $path/config |
391 | chown -R $mapped_uid $STATE_DIR | |
392 | chown -R $mapped_uid $cache | |
57d116ab | 393 | fi |
2133f58c SH |
394 | if [ $mapped_gid -ne -1 ]; then |
395 | chgrp $mapped_gid $path/config | |
396 | chgrp -R $mapped_gid $STATE_DIR | |
397 | chgrp -R $mapped_gid $cache | |
398 | fi | |
57d116ab | 399 | |
d1458ac8 SH |
400 | echo "Container $name created." |
401 | exit 0 | |
b942e672 SM |
402 | |
403 | # vi: ts=4 expandtab |