]>
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" | |
27 | ||
d1458ac8 SH |
28 | if [ -r /etc/default/lxc ]; then |
29 | . /etc/default/lxc | |
30 | fi | |
31 | ||
1aad9e44 SH |
32 | am_in_userns() { |
33 | [ -e /proc/self/uid_map ] || { echo no; return; } | |
34 | [ "$(wc -l /proc/self/uid_map | awk '{ print $1 }')" -eq 1 ] || { echo yes; return; } | |
35 | line=$(awk '{ print $1 " " $2 " " $3 }' /proc/self/uid_map) | |
36 | [ "$line" = "0 0 4294967295" ] && { echo no; return; } | |
37 | echo yes | |
38 | } | |
39 | ||
40 | in_userns=0 | |
41 | [ $(am_in_userns) = "yes" ] && in_userns=1 | |
1aad9e44 | 42 | |
d1458ac8 SH |
43 | copy_configuration() |
44 | { | |
45 | path=$1 | |
46 | rootfs=$2 | |
47 | name=$3 | |
48 | arch=$4 | |
42ff5f0f | 49 | release=$5 |
d1458ac8 SH |
50 | |
51 | if [ $arch = "i386" ]; then | |
52 | arch="i686" | |
53 | fi | |
54 | ||
4759162d SH |
55 | # if there is exactly one veth network entry, make sure it has an |
56 | # associated hwaddr. | |
57 | nics=`grep -e '^lxc\.network\.type[ \t]*=[ \t]*veth' $path/config | wc -l` | |
58 | if [ $nics -eq 1 ]; then | |
daaf41b3 | 59 | 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 |
60 | fi |
61 | ||
1881820a | 62 | grep -q "^lxc.rootfs" $path/config 2>/dev/null || echo "lxc.rootfs = $rootfs" >> $path/config |
d1458ac8 | 63 | cat <<EOF >> $path/config |
daaf41b3 SG |
64 | lxc.mount = $path/fstab |
65 | lxc.pivotdir = lxc_putold | |
d1458ac8 | 66 | |
daaf41b3 | 67 | lxc.devttydir =$ttydir |
d1458ac8 SH |
68 | lxc.tty = 4 |
69 | lxc.pts = 1024 | |
daaf41b3 SG |
70 | |
71 | lxc.utsname = $name | |
d1458ac8 | 72 | lxc.arch = $arch |
eee3ba81 | 73 | lxc.cap.drop = sys_module mac_admin mac_override sys_time |
d1458ac8 | 74 | |
f02ce27d SG |
75 | # When using LXC with apparmor, uncomment the next line to run unconfined: |
76 | #lxc.aa_profile = unconfined | |
9313e1e6 | 77 | |
b85ab798 SH |
78 | # To support container nesting on an Ubuntu host, uncomment next two lines: |
79 | #lxc.aa_profile = lxc-container-default-with-nesting | |
80 | #lxc.hook.mount = /usr/share/lxc/hooks/mountcgroups | |
f02ce27d | 81 | |
65d8ae9c | 82 | lxc.hook.clone = ${CLONE_HOOK_FN} |
57d116ab | 83 | EOF |
65d8ae9c | 84 | |
57d116ab SH |
85 | # can't write to devices.deny without CAP_SYS_ADMIN in init-user-ns |
86 | if [ $in_userns -ne 1 ]; then | |
87 | cat <<EOF >> $path/config | |
d1458ac8 SH |
88 | lxc.cgroup.devices.deny = a |
89 | # Allow any mknod (but not using the node) | |
90 | lxc.cgroup.devices.allow = c *:* m | |
91 | lxc.cgroup.devices.allow = b *:* m | |
92 | # /dev/null and zero | |
93 | lxc.cgroup.devices.allow = c 1:3 rwm | |
94 | lxc.cgroup.devices.allow = c 1:5 rwm | |
95 | # consoles | |
96 | lxc.cgroup.devices.allow = c 5:1 rwm | |
97 | lxc.cgroup.devices.allow = c 5:0 rwm | |
d1458ac8 SH |
98 | # /dev/{,u}random |
99 | lxc.cgroup.devices.allow = c 1:9 rwm | |
100 | lxc.cgroup.devices.allow = c 1:8 rwm | |
101 | lxc.cgroup.devices.allow = c 136:* rwm | |
102 | lxc.cgroup.devices.allow = c 5:2 rwm | |
103 | # rtc | |
eee3ba81 | 104 | lxc.cgroup.devices.allow = c 254:0 rm |
a2abaa9e | 105 | # fuse |
d1458ac8 | 106 | lxc.cgroup.devices.allow = c 10:229 rwm |
a2abaa9e | 107 | # tun |
d1458ac8 | 108 | lxc.cgroup.devices.allow = c 10:200 rwm |
a2abaa9e | 109 | # full |
d1458ac8 | 110 | lxc.cgroup.devices.allow = c 1:7 rwm |
a2abaa9e | 111 | # hpet |
d1458ac8 | 112 | lxc.cgroup.devices.allow = c 10:228 rwm |
a2abaa9e | 113 | # kvm |
d1458ac8 SH |
114 | lxc.cgroup.devices.allow = c 10:232 rwm |
115 | EOF | |
57d116ab | 116 | fi |
d1458ac8 SH |
117 | |
118 | cat <<EOF > $path/fstab | |
80a881b2 SH |
119 | proc proc proc nodev,noexec,nosuid 0 0 |
120 | sysfs sys sysfs defaults 0 0 | |
d59feca3 SG |
121 | /sys/fs/fuse/connections sys/fs/fuse/connections none bind,optional 0 0 |
122 | /sys/kernel/debug sys/kernel/debug none bind,optional 0 0 | |
123 | /sys/kernel/security sys/kernel/security none bind,optional 0 0 | |
4d7bcfb6 | 124 | /sys/fs/pstore sys/fs/pstore none bind,optional 0 0 |
bf7d76cf | 125 | EOF |
d1458ac8 | 126 | |
1aad9e44 SH |
127 | # unprivileged user can't mknod these. One day we may allow |
128 | # that in the kernel, but not right now. So let's just bind | |
129 | # mount the files from the host. | |
130 | if [ $in_userns -eq 1 ]; then | |
57d116ab | 131 | mkdir -p $rootfs/dev/pts |
1aad9e44 SH |
132 | for dev in null tty urandom console; do |
133 | touch $rootfs/dev/$dev | |
134 | echo "/dev/$dev dev/$dev none bind 0 0" >> $path/fstab | |
135 | done | |
136 | fi | |
137 | ||
542939c3 | 138 | # rmdir /dev/shm for containers that have /run/shm |
42ff5f0f SH |
139 | # I'm afraid of doing rm -rf $rootfs/dev/shm, in case it did |
140 | # get bind mounted to the host's /run/shm. So try to rmdir | |
141 | # it, and in case that fails move it out of the way. | |
542939c3 SG |
142 | if [ ! -L $rootfs/dev/shm ] && [ -d $rootfs/run/shm ] && [ -e $rootfs/dev/shm ]; then |
143 | mv $rootfs/dev/shm $rootfs/dev/shm.bak | |
42ff5f0f SH |
144 | ln -s /run/shm $rootfs/dev/shm |
145 | fi | |
146 | ||
d1458ac8 SH |
147 | return 0 |
148 | } | |
149 | ||
150 | usage() | |
151 | { | |
152 | cat <<EOF | |
4759162d SH |
153 | LXC Container configuration for Ubuntu Cloud images. |
154 | ||
155 | Generic Options | |
156 | [ -r | --release <release> ]: Release name of container, defaults to host | |
1897e3bc | 157 | [ --rootfs <path> ]: Path in which rootfs will be placed |
840295ff | 158 | [ -a | --arch ]: Arhcitecture of container, defaults to host architecture |
4759162d | 159 | [ -T | --tarball ]: Location of tarball |
52c8f624 | 160 | [ -d | --debug ]: Run with 'set -x' to debug errors |
427bffc7 | 161 | [ -s | --stream]: Use specified stream rather than 'released' |
4759162d | 162 | |
65d8ae9c SM |
163 | Additionally, clone hooks can be passed through (ie, --userdata). For those, |
164 | see: | |
165 | $CLONE_HOOK_FN --help | |
d1458ac8 SH |
166 | EOF |
167 | return 0 | |
168 | } | |
169 | ||
57d116ab | 170 | 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: -- "$@") |
d1458ac8 SH |
171 | if [ $? -ne 0 ]; then |
172 | usage $(basename $0) | |
173 | exit 1 | |
174 | fi | |
175 | eval set -- "$options" | |
176 | ||
57d116ab | 177 | mapped_uid=-1 |
65d8ae9c SM |
178 | # default release is precise, or the systems release if recognized |
179 | release=precise | |
d1458ac8 SH |
180 | if [ -f /etc/lsb-release ]; then |
181 | . /etc/lsb-release | |
65d8ae9c SM |
182 | rels=$(ubuntu-distro-info --supported 2>/dev/null) || |
183 | rels="lucid natty oneiric precise quantal raring saucy" | |
184 | for r in $rels; do | |
185 | [ "$DISTRIB_CODENAME" = "$r" ] && release="$r" | |
186 | done | |
d1458ac8 SH |
187 | fi |
188 | ||
d1458ac8 SH |
189 | # Code taken from debootstrap |
190 | if [ -x /usr/bin/dpkg ] && /usr/bin/dpkg --print-architecture >/dev/null 2>&1; then | |
191 | arch=`/usr/bin/dpkg --print-architecture` | |
192 | elif type udpkg >/dev/null 2>&1 && udpkg --print-architecture >/dev/null 2>&1; then | |
193 | arch=`/usr/bin/udpkg --print-architecture` | |
194 | else | |
ed4616b1 | 195 | arch=$(uname -m) |
d1458ac8 SH |
196 | if [ "$arch" = "i686" ]; then |
197 | arch="i386" | |
198 | elif [ "$arch" = "x86_64" ]; then | |
199 | arch="amd64" | |
200 | elif [ "$arch" = "armv7l" ]; then | |
3eecde70 | 201 | # note: arm images don't exist before oneiric; are called armhf in |
b8bced69 | 202 | # precise and later; and are not supported by the query, so we don't actually |
3eecde70 SH |
203 | # support them yet (see check later on). When Query2 is available, |
204 | # we'll use that to enable arm images. | |
8a63c0a9 | 205 | arch="armhf" |
d1458ac8 SH |
206 | fi |
207 | fi | |
208 | ||
52c8f624 | 209 | debug=0 |
d1458ac8 SH |
210 | hostarch=$arch |
211 | cloud=0 | |
4759162d | 212 | locales=1 |
d1458ac8 | 213 | flushcache=0 |
427bffc7 | 214 | stream="released" |
65d8ae9c | 215 | cloneargs=() |
d1458ac8 SH |
216 | while true |
217 | do | |
218 | case "$1" in | |
4759162d SH |
219 | -h|--help) usage $0 && exit 0;; |
220 | -p|--path) path=$2; shift 2;; | |
221 | -n|--name) name=$2; shift 2;; | |
222 | -F|--flush-cache) flushcache=1; shift 1;; | |
223 | -r|--release) release=$2; shift 2;; | |
224 | -a|--arch) arch=$2; shift 2;; | |
4759162d | 225 | -T|--tarball) tarball=$2; shift 2;; |
52c8f624 | 226 | -d|--debug) debug=1; shift 1;; |
427bffc7 | 227 | -s|--stream) stream=$2; shift 2;; |
65d8ae9c SM |
228 | --rootfs) rootfs=$2; shift 2;; |
229 | -L|--no?locales) cloneargs[${#cloneargs[@]}]="--no-locales"; shift 1;; | |
230 | -i|--hostid) cloneargs[${#cloneargs[@]}]="--hostid=$2"; shift 2;; | |
231 | -u|--userdata) cloneargs[${#cloneargs[@]}]="--userdata=$2"; shift 2;; | |
232 | -C|--cloud) cloneargs[${#cloneargs[@]}]="--cloud"; shift 1;; | |
233 | -S|--auth-key) cloneargs[${#cloneargs[@]}]="--auth-key=$2"; shift 2;; | |
57d116ab | 234 | --mapped-uid) mapped_uid=$2; shift 2;; |
4759162d | 235 | --) shift 1; break ;; |
d1458ac8 SH |
236 | *) break ;; |
237 | esac | |
238 | done | |
239 | ||
57d116ab | 240 | echo "mapped_uid is .$mapped_uid." |
65d8ae9c SM |
241 | cloneargs=( "--name=$name" "${cloneargs[@]}" ) |
242 | ||
3eecde70 | 243 | if [ $debug -eq 1 ]; then |
52c8f624 SH |
244 | set -x |
245 | fi | |
246 | ||
d1458ac8 SH |
247 | if [ "$arch" == "i686" ]; then |
248 | arch=i386 | |
249 | fi | |
250 | ||
8a63c0a9 SG |
251 | if [ $arch != "i386" -a $arch != "amd64" -a $arch != "armhf" -a $arch != "armel" ]; then |
252 | echo "Only i386, amd64, armel and armhf are supported by the ubuntu cloud template." | |
d1458ac8 SH |
253 | exit 1 |
254 | fi | |
255 | ||
8a63c0a9 SG |
256 | if [ $hostarch != "i386" -a $hostarch != "amd64" -a $hostarch != "armhf" -a $hostarch != "armel" ]; then |
257 | echo "Only i386, amd64, armel and armhf are supported as host." | |
258 | exit 1 | |
259 | fi | |
260 | ||
261 | if [ $hostarch = "amd64" -a $arch != "amd64" -a $arch != "i386" ]; then | |
262 | echo "can't create $arch container on $hostarch" | |
263 | exit 1 | |
264 | fi | |
265 | ||
266 | if [ $hostarch = "i386" -a $arch != "i386" ]; then | |
267 | echo "can't create $arch container on $hostarch" | |
268 | exit 1 | |
269 | fi | |
270 | ||
271 | if [ $hostarch = "armhf" -o $hostarch = "armel" ] && \ | |
272 | [ $arch != "armhf" -a $arch != "armel" ]; then | |
273 | echo "can't create $arch container on $hostarch" | |
d1458ac8 SH |
274 | exit 1 |
275 | fi | |
276 | ||
427bffc7 SH |
277 | if [ "$stream" != "daily" -a "$stream" != "released" ]; then |
278 | echo "Only 'daily' and 'released' streams are supported" | |
279 | exit 1 | |
280 | fi | |
281 | ||
d1458ac8 SH |
282 | if [ -z "$path" ]; then |
283 | echo "'path' parameter is required" | |
284 | exit 1 | |
285 | fi | |
286 | ||
287 | if [ "$(id -u)" != "0" ]; then | |
288 | echo "This script should be run as 'root'" | |
289 | exit 1 | |
290 | fi | |
291 | ||
1881820a SH |
292 | # detect rootfs |
293 | config="$path/config" | |
1897e3bc SH |
294 | if [ -z "$rootfs" ]; then |
295 | if grep -q '^lxc.rootfs' $config 2>/dev/null ; then | |
853d58fd | 296 | rootfs=$(awk -F= '/^lxc.rootfs =/{ print $2 }' $config) |
1897e3bc SH |
297 | else |
298 | rootfs=$path/rootfs | |
299 | fi | |
1881820a | 300 | fi |
d1458ac8 SH |
301 | |
302 | type ubuntu-cloudimg-query | |
303 | type wget | |
304 | ||
305 | # determine the url, tarball, and directory names | |
306 | # download if needed | |
65d8ae9c | 307 | cache="$STATE_DIR/cache/lxc/cloud-$release" |
57d116ab SH |
308 | STATE_DIR="$HOME/.cache/lxc/" |
309 | cache="$HOME/.cache/lxc/cloud-$release" | |
d1458ac8 SH |
310 | |
311 | mkdir -p $cache | |
312 | ||
4759162d | 313 | if [ -n "$tarball" ]; then |
b942e672 | 314 | url2="$tarball" |
4759162d | 315 | else |
b942e672 SM |
316 | url1=`ubuntu-cloudimg-query $release $stream $arch --format "%{url}\n"` |
317 | url2=`echo $url1 | sed -e 's/.tar.gz/-root\0/'` | |
4759162d SH |
318 | fi |
319 | ||
d1458ac8 SH |
320 | filename=`basename $url2` |
321 | ||
f1ccde27 SH |
322 | wgetcleanup() |
323 | { | |
b942e672 | 324 | rm -f $filename |
f1ccde27 SH |
325 | } |
326 | ||
3eecde70 SH |
327 | buildcleanup() |
328 | { | |
329 | cd $rootfs | |
330 | umount -l $cache/$xdir || true | |
331 | rm -rf $cache | |
332 | } | |
333 | ||
334 | # if the release doesn't have a *-rootfs.tar.gz, then create one from the | |
335 | # cloudimg.tar.gz by extracting the .img, mounting it loopback, and creating | |
336 | # a tarball from the mounted image. | |
337 | build_root_tgz() | |
338 | { | |
339 | url=$1 | |
340 | filename=$2 | |
341 | ||
342 | xdir=`mktemp -d -p .` | |
343 | tarname=`basename $url` | |
edd3810e | 344 | imgname="$release-*-cloudimg-$arch.img" |
f1ccde27 | 345 | trap buildcleanup EXIT SIGHUP SIGINT SIGTERM |
3eecde70 SH |
346 | if [ $flushcache -eq 1 -o ! -f $cache/$tarname ]; then |
347 | rm -f $tarname | |
348 | echo "Downloading cloud image from $url" | |
349 | wget $url || { echo "Couldn't find cloud image $url."; exit 1; } | |
350 | fi | |
351 | echo "Creating new cached cloud image rootfs" | |
9c3bc32c | 352 | tar --wildcards -zxf "$tarname" "$imgname" |
3eecde70 | 353 | mount -o loop $imgname $xdir |
9c3bc32c | 354 | (cd $xdir; tar --numeric-owner -cpzf "../$filename" .) |
3eecde70 SH |
355 | umount $xdir |
356 | rm -f $tarname $imgname | |
357 | rmdir $xdir | |
358 | echo "New cloud image cache created" | |
359 | trap EXIT | |
f1ccde27 SH |
360 | trap SIGHUP |
361 | trap SIGINT | |
362 | trap SIGTERM | |
3eecde70 SH |
363 | } |
364 | ||
1aad9e44 | 365 | do_extract_rootfs() { |
d1458ac8 SH |
366 | |
367 | cd $cache | |
368 | if [ $flushcache -eq 1 ]; then | |
369 | echo "Clearing the cached images" | |
370 | rm -f $filename | |
371 | fi | |
372 | ||
f1ccde27 | 373 | trap wgetcleanup EXIT SIGHUP SIGINT SIGTERM |
d1458ac8 | 374 | if [ ! -f $filename ]; then |
b942e672 | 375 | wget $url2 || build_root_tgz $url1 $filename |
d1458ac8 | 376 | fi |
f1ccde27 SH |
377 | trap EXIT |
378 | trap SIGHUP | |
379 | trap SIGINT | |
380 | trap SIGTERM | |
d1458ac8 | 381 | |
3eecde70 | 382 | echo "Extracting container rootfs" |
d1458ac8 SH |
383 | mkdir -p $rootfs |
384 | cd $rootfs | |
57d116ab SH |
385 | if [ $in_userns -eq 1 ]; then |
386 | tar --anchored --exclude="dev/*" --numeric-owner -xpzf "$cache/$filename" | |
387 | else | |
388 | tar --numeric-owner -xpzf "$cache/$filename" | |
389 | fi | |
1aad9e44 SH |
390 | } |
391 | ||
392 | if [ -n "$tarball" ]; then | |
393 | do_extract_rootfs | |
394 | else | |
65d8ae9c | 395 | mkdir -p "$STATE_DIR/lock/subsys/" |
1aad9e44 SH |
396 | ( |
397 | flock -x 200 | |
398 | do_extract_rootfs | |
65d8ae9c | 399 | ) 200>"$STATE_DIR/lock/subsys/lxc-ubuntu-cloud" |
1aad9e44 | 400 | fi |
d1458ac8 | 401 | |
42ff5f0f | 402 | copy_configuration $path $rootfs $name $arch $release |
d1458ac8 | 403 | |
65d8ae9c SM |
404 | "$CLONE_HOOK_FN" "${cloneargs[@]}" "$rootfs" |
405 | ||
57d116ab | 406 | if [ $mapped_uid -ne -1 ]; then |
c01c25fc SG |
407 | chown $mapped_uid $path/config |
408 | chown -R $mapped_uid $STATE_DIR | |
409 | chown -R $mapped_uid $cache | |
57d116ab SH |
410 | fi |
411 | ||
d1458ac8 SH |
412 | echo "Container $name created." |
413 | exit 0 | |
b942e672 SM |
414 | |
415 | # vi: ts=4 expandtab |