]>
Commit | Line | Data |
---|---|---|
3f033aa8 WM |
1 | #!/bin/bash |
2 | ||
3 | # | |
e2b4064f | 4 | # template script for generating ubuntu container for LXC |
3f033aa8 | 5 | # |
e2b4064f | 6 | # This script consolidates and extends the existing lxc ubuntu scripts |
3f033aa8 WM |
7 | # |
8 | ||
8cd80b50 SG |
9 | # Copyright © 2011 Serge Hallyn <serge.hallyn@canonical.com> |
10 | # Copyright © 2010 Wilhelm Meier | |
3f033aa8 WM |
11 | # Author: Wilhelm Meier <wilhelm.meier@fh-kl.de> |
12 | # | |
acbb59f5 SH |
13 | # This library is free software; you can redistribute it and/or |
14 | # modify it under the terms of the GNU Lesser General Public | |
15 | # License as published by the Free Software Foundation; either | |
16 | # version 2.1 of the License, or (at your option) any later version. | |
17 | ||
18 | # This library is distributed in the hope that it will be useful, | |
19 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
20 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
21 | # Lesser General Public License for more details. | |
22 | ||
23 | # You should have received a copy of the GNU Lesser General Public | |
24 | # License along with this library; if not, write to the Free Software | |
25 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | |
3f033aa8 | 26 | |
8ec981fc | 27 | # Detect use under userns (unsupported) |
c63c04fc | 28 | for arg in "$@"; do |
96283b54 SG |
29 | [ "$arg" = "--" ] && break |
30 | if [ "$arg" = "--mapped-uid" -o "$arg" = "--mapped-gid" ]; then | |
8ec981fc SG |
31 | echo "This template can't be used for unprivileged containers." 1>&2 |
32 | echo "You may want to try the \"download\" template instead." 1>&2 | |
33 | exit 1 | |
34 | fi | |
35 | done | |
36 | ||
207bf0e4 SG |
37 | # Make sure the usual locations are in PATH |
38 | export PATH=$PATH:/usr/sbin:/usr/bin:/sbin:/bin | |
39 | ||
1d61e5b9 SH |
40 | set -e |
41 | ||
f2a95ee1 SG |
42 | LOCALSTATEDIR="@LOCALSTATEDIR@" |
43 | LXC_TEMPLATE_CONFIG="@LXCTEMPLATECONFIG@" | |
6dc6f80b KC |
44 | # Allows the lxc-cache directory to be set by environment variable |
45 | LXC_CACHE_PATH=${LXC_CACHE_PATH:-"$LOCALSTATEDIR/cache/lxc"} | |
703d065d | 46 | |
e2b4064f SH |
47 | if [ -r /etc/default/lxc ]; then |
48 | . /etc/default/lxc | |
49 | fi | |
50 | ||
654bf1af | 51 | # Check if given path is in a btrfs partition |
5652d610 JM |
52 | is_btrfs() |
53 | { | |
654bf1af JM |
54 | [ -e $1 -a $(stat -f -c '%T' $1) = "btrfs" ] |
55 | } | |
56 | ||
57 | # Check if given path is the root of a btrfs subvolume | |
5652d610 JM |
58 | is_btrfs_subvolume() |
59 | { | |
654bf1af JM |
60 | [ -d $1 -a $(stat -f -c '%T' $1) = "btrfs" -a $(stat -c '%i' $1) -eq 256 ] |
61 | } | |
62 | ||
5652d610 JM |
63 | try_mksubvolume() |
64 | { | |
654bf1af JM |
65 | path=$1 |
66 | [ -d $path ] && return 0 | |
67 | mkdir -p $(dirname $path) | |
206a255e | 68 | if which btrfs >/dev/null 2>&1 && is_btrfs $(dirname $path); then |
654bf1af JM |
69 | btrfs subvolume create $path |
70 | else | |
71 | mkdir -p $path | |
72 | fi | |
73 | } | |
74 | ||
5652d610 JM |
75 | try_rmsubvolume() |
76 | { | |
654bf1af JM |
77 | path=$1 |
78 | [ -d $path ] || return 0 | |
206a255e | 79 | if which btrfs >/dev/null 2>&1 && is_btrfs_subvolume $path; then |
654bf1af JM |
80 | btrfs subvolume delete $path |
81 | else | |
82 | rm -rf $path | |
83 | fi | |
84 | } | |
85 | ||
3f033aa8 WM |
86 | configure_ubuntu() |
87 | { | |
88 | rootfs=$1 | |
89 | hostname=$2 | |
f6144f0c | 90 | release=$3 |
2004e7da GL |
91 | user=$4 |
92 | password=$5 | |
3f033aa8 | 93 | |
a2abaa9e | 94 | # configure the network using the dhcp |
db3c8336 | 95 | if chroot $rootfs which netplan >/dev/null 2>&1; then |
649f249c DJL |
96 | cat <<EOF > $rootfs/etc/netplan/10-lxc.yaml |
97 | network: | |
98 | ethernets: | |
99 | eth0: {dhcp4: true} | |
100 | version: 2 | |
101 | EOF | |
102 | else | |
103 | cat <<EOF > $rootfs/etc/network/interfaces | |
adca8543 SH |
104 | # This file describes the network interfaces available on your system |
105 | # and how to activate them. For more information, see interfaces(5). | |
106 | ||
107 | # The loopback network interface | |
3f033aa8 WM |
108 | auto lo |
109 | iface lo inet loopback | |
110 | ||
111 | auto eth0 | |
112 | iface eth0 inet dhcp | |
113 | EOF | |
649f249c | 114 | fi |
3f033aa8 WM |
115 | |
116 | # set the hostname | |
117 | cat <<EOF > $rootfs/etc/hostname | |
118 | $hostname | |
119 | EOF | |
120 | # set minimal hosts | |
121 | cat <<EOF > $rootfs/etc/hosts | |
adca8543 SH |
122 | 127.0.0.1 localhost |
123 | 127.0.1.1 $hostname | |
124 | ||
125 | # The following lines are desirable for IPv6 capable hosts | |
126 | ::1 ip6-localhost ip6-loopback | |
127 | fe00::0 ip6-localnet | |
128 | ff00::0 ip6-mcastprefix | |
129 | ff02::1 ip6-allnodes | |
130 | ff02::2 ip6-allrouters | |
3f033aa8 WM |
131 | EOF |
132 | ||
adca8543 | 133 | if [ ! -f $rootfs/etc/init/container-detect.conf ]; then |
f6144f0c SH |
134 | # suppress log level output for udev |
135 | sed -i "s/=\"err\"/=0/" $rootfs/etc/udev/udev.conf | |
3f033aa8 | 136 | |
f6144f0c SH |
137 | # remove jobs for consoles 5 and 6 since we only create 4 consoles in |
138 | # this template | |
139 | rm -f $rootfs/etc/init/tty{5,6}.conf | |
140 | fi | |
3f033aa8 | 141 | |
ce5dbd82 | 142 | if [ -z "$bindhome" ]; then |
2004e7da GL |
143 | chroot $rootfs useradd --create-home -s /bin/bash $user |
144 | echo "$user:$password" | chroot $rootfs chpasswd | |
ce5dbd82 SH |
145 | fi |
146 | ||
18f823c1 | 147 | # make sure we have the current locale defined in the container |
dc7f6545 | 148 | if [ -z "$LANG" ] || echo $LANG | grep -E -q "^C(\..+)*$"; then |
3fefd6e6 SG |
149 | chroot $rootfs locale-gen en_US.UTF-8 || true |
150 | chroot $rootfs update-locale LANG=en_US.UTF-8 || true | |
18f823c1 | 151 | else |
3fefd6e6 SG |
152 | chroot $rootfs locale-gen $LANG || true |
153 | chroot $rootfs update-locale LANG=$LANG || true | |
18f823c1 SG |
154 | fi |
155 | ||
a2abaa9e | 156 | # generate new SSH keys |
07219a02 | 157 | if [ -x $rootfs/var/lib/dpkg/info/openssh-server.postinst ]; then |
6cda3f5a SG |
158 | cat > $rootfs/usr/sbin/policy-rc.d << EOF |
159 | #!/bin/sh | |
160 | exit 101 | |
161 | EOF | |
162 | chmod +x $rootfs/usr/sbin/policy-rc.d | |
163 | ||
4a1bd8d6 DJL |
164 | if [ -f "$rootfs/etc/init/ssh.conf" ]; then |
165 | mv "$rootfs/etc/init/ssh.conf" "$rootfs/etc/init/ssh.conf.disabled" | |
166 | fi | |
167 | ||
a2abaa9e | 168 | rm -f $rootfs/etc/ssh/ssh_host_*key* |
4a1bd8d6 | 169 | |
07219a02 | 170 | DPKG_MAINTSCRIPT_PACKAGE=openssh DPKG_MAINTSCRIPT_NAME=postinst chroot $rootfs /var/lib/dpkg/info/openssh-server.postinst configure |
6cda3f5a | 171 | |
c5d32181 SG |
172 | sed -i "s/root@$(hostname)/root@$hostname/g" $rootfs/etc/ssh/ssh_host_*.pub |
173 | ||
4a1bd8d6 DJL |
174 | if [ -f "$rootfs/etc/init/ssh.conf.disabled" ]; then |
175 | mv "$rootfs/etc/init/ssh.conf.disabled" "$rootfs/etc/init/ssh.conf" | |
176 | fi | |
177 | ||
6cda3f5a | 178 | rm -f $rootfs/usr/sbin/policy-rc.d |
a2abaa9e SG |
179 | fi |
180 | ||
ce5dbd82 SH |
181 | return 0 |
182 | } | |
183 | ||
184 | # finish setting up the user in the container by injecting ssh key and | |
185 | # adding sudo group membership. | |
186 | # passed-in user is either 'ubuntu' or the user to bind in from host. | |
187 | finalize_user() | |
188 | { | |
189 | user=$1 | |
190 | ||
adca8543 SH |
191 | sudo_version=$(chroot $rootfs dpkg-query -W -f='${Version}' sudo) |
192 | ||
193 | if chroot $rootfs dpkg --compare-versions $sudo_version gt "1.8.3p1-1"; then | |
ce5dbd82 SH |
194 | groups="sudo" |
195 | else | |
196 | groups="sudo admin" | |
197 | fi | |
96bd45c8 SG |
198 | |
199 | for group in $groups; do | |
200 | chroot $rootfs groupadd --system $group >/dev/null 2>&1 || true | |
ce5dbd82 | 201 | chroot $rootfs adduser ${user} $group >/dev/null 2>&1 || true |
96bd45c8 SG |
202 | done |
203 | ||
4759162d | 204 | if [ -n "$auth_key" -a -f "$auth_key" ]; then |
1e1f8eeb SG |
205 | u_path="/home/${user}/.ssh" |
206 | root_u_path="$rootfs/$u_path" | |
207 | mkdir -p $root_u_path | |
208 | cp $auth_key "$root_u_path/authorized_keys" | |
209 | chroot $rootfs chown -R ${user}: "$u_path" | |
4759162d | 210 | |
1e1f8eeb | 211 | echo "Inserted SSH public key from $auth_key into /home/${user}/.ssh/authorized_keys" |
4759162d | 212 | fi |
3a3ba44a | 213 | return 0 |
3f033aa8 WM |
214 | } |
215 | ||
4213a747 CG |
216 | # A function to try and autodetect squid-deb-proxy servers on the local network |
217 | # if either the squid-deb-proxy-client package is installed on the host or | |
218 | # a parent container set the 50squid-deb-proxy-client file. | |
219 | squid_deb_proxy_autodetect() | |
220 | { | |
221 | local apt_discover=/usr/share/squid-deb-proxy-client/apt-avahi-discover | |
222 | local proxy_file=/etc/apt/apt.conf.d/50squid-deb-proxy-client | |
223 | squid_proxy_line= # That's a global :/ | |
224 | ||
225 | # Maybe the host is aware of a squid-deb-proxy? | |
226 | if [ -f $apt_discover ]; then | |
227 | echo -n "Discovering squid-deb-proxy..." | |
228 | squid_proxy_line=$($apt_discover) | |
229 | if [ -n "$squid_proxy_line" ]; then | |
230 | echo "found squid-deb-proxy: $squid_proxy_line" | |
231 | else | |
232 | echo "no squid-deb-proxy found" | |
233 | fi | |
234 | fi | |
235 | ||
236 | # Are we in a nested container, and the parent already knows of a proxy? | |
237 | if [ -f $proxy_file ]; then | |
238 | # Extract the squid URL from the file (whatever is between "") | |
239 | squid_proxy_line=`cat $proxy_file | sed "s/.*\"\(.*\)\".*/\1/"` | |
240 | fi | |
241 | } | |
242 | ||
cf0f9033 SH |
243 | # |
244 | # Choose proxies for container | |
245 | # http_proxy will be used by debootstrap on the host. | |
246 | # APT_PROXY will be used to set /etc/apt/apt.conf.d/70proxy in the container. | |
247 | # | |
248 | choose_container_proxy() | |
249 | { | |
250 | local rootfs=$1 | |
251 | local arch=$2 | |
252 | ||
253 | if [ -z "$HTTP_PROXY" ]; then | |
254 | HTTP_PROXY="none" | |
255 | fi | |
256 | case "$HTTP_PROXY" in | |
257 | none) | |
4213a747 CG |
258 | squid_deb_proxy_autodetect |
259 | if [ -n "$squid_proxy_line" ]; then | |
260 | APT_PROXY=$squid_proxy_line | |
261 | export http_proxy=$squid_proxy_line | |
262 | else | |
263 | APT_PROXY= | |
264 | fi | |
cf0f9033 SH |
265 | ;; |
266 | apt) | |
267 | RES=`apt-config shell APT_PROXY Acquire::http::Proxy` | |
268 | eval $RES | |
269 | [ -z "$APT_PROXY" ] || export http_proxy=$APT_PROXY | |
270 | ;; | |
271 | *) | |
272 | APT_PROXY=$HTTP_PROXY | |
273 | export http_proxy=$HTTP_PROXY | |
274 | ;; | |
275 | esac | |
276 | } | |
277 | ||
5a50e09a SH |
278 | write_sourceslist() |
279 | { | |
df7216f6 | 280 | # $1 => path to the partial cache or the rootfs |
5a50e09a SH |
281 | # $2 => architecture we want to add |
282 | # $3 => whether to use the multi-arch syntax or not | |
283 | ||
cf0f9033 | 284 | if [ -n "$APT_PROXY" ]; then |
df7216f6 SD |
285 | mkdir -p $1/etc/apt/apt.conf.d |
286 | cat > $1/etc/apt/apt.conf.d/70proxy << EOF | |
cf0f9033 SH |
287 | Acquire::http::Proxy "$APT_PROXY" ; |
288 | EOF | |
289 | fi | |
290 | ||
5a50e09a SH |
291 | case $2 in |
292 | amd64|i386) | |
293 | MIRROR=${MIRROR:-http://archive.ubuntu.com/ubuntu} | |
294 | SECURITY_MIRROR=${SECURITY_MIRROR:-http://security.ubuntu.com/ubuntu} | |
295 | ;; | |
5a50e09a SH |
296 | *) |
297 | MIRROR=${MIRROR:-http://ports.ubuntu.com/ubuntu-ports} | |
298 | SECURITY_MIRROR=${SECURITY_MIRROR:-http://ports.ubuntu.com/ubuntu-ports} | |
299 | ;; | |
300 | esac | |
301 | if [ -n "$3" ]; then | |
302 | cat >> "$1/etc/apt/sources.list" << EOF | |
303 | deb [arch=$2] $MIRROR ${release} main restricted universe multiverse | |
304 | deb [arch=$2] $MIRROR ${release}-updates main restricted universe multiverse | |
305 | deb [arch=$2] $SECURITY_MIRROR ${release}-security main restricted universe multiverse | |
306 | EOF | |
307 | else | |
308 | cat >> "$1/etc/apt/sources.list" << EOF | |
309 | deb $MIRROR ${release} main restricted universe multiverse | |
310 | deb $MIRROR ${release}-updates main restricted universe multiverse | |
311 | deb $SECURITY_MIRROR ${release}-security main restricted universe multiverse | |
312 | EOF | |
313 | fi | |
314 | } | |
315 | ||
28b62856 GL |
316 | install_packages() |
317 | { | |
318 | local rootfs="$1" | |
319 | shift | |
320 | local packages="$*" | |
321 | if [ -z $update ] | |
322 | then | |
323 | chroot $rootfs apt-get update | |
324 | update=true | |
325 | fi | |
326 | if [ -n "${packages}" ] | |
327 | then | |
328 | chroot $rootfs apt-get install --force-yes -y --no-install-recommends ${packages} | |
329 | fi | |
330 | } | |
331 | ||
f1ccde27 SH |
332 | cleanup() |
333 | { | |
654bf1af JM |
334 | try_rmsubvolume $cache/partial-$arch |
335 | try_rmsubvolume $cache/rootfs-$arch | |
f1ccde27 SH |
336 | } |
337 | ||
91a5df88 SH |
338 | suggest_flush() |
339 | { | |
340 | echo "Container upgrade failed. The container cache may be out of date," | |
3f5f5d99 | 341 | echo "in which case flushing the cache (see -F in the help output) may help." |
91a5df88 SH |
342 | } |
343 | ||
3f033aa8 WM |
344 | download_ubuntu() |
345 | { | |
3f033aa8 WM |
346 | cache=$1 |
347 | arch=$2 | |
e2b4064f | 348 | release=$3 |
28b62856 | 349 | |
aec6a205 SG |
350 | case $2 in |
351 | amd64|i386) | |
352 | MIRROR=${MIRROR:-http://archive.ubuntu.com/ubuntu} | |
353 | SECURITY_MIRROR=${SECURITY_MIRROR:-http://security.ubuntu.com/ubuntu} | |
354 | ;; | |
355 | *) | |
356 | MIRROR=${MIRROR:-http://ports.ubuntu.com/ubuntu-ports} | |
357 | SECURITY_MIRROR=${SECURITY_MIRROR:-http://ports.ubuntu.com/ubuntu-ports} | |
358 | ;; | |
359 | esac | |
360 | ||
d0e206b8 | 361 | packages_template=${packages_template:-"apt-transport-https,ssh,vim"} |
88753f7a | 362 | debootstrap_parameters= |
fade719e SG |
363 | |
364 | # Try to guess a list of langpacks to install | |
365 | langpacks="language-pack-en" | |
366 | ||
367 | if which dpkg >/dev/null 2>&1; then | |
89573feb | 368 | langpacks=`(echo $langpacks && |
fade719e SG |
369 | dpkg -l | grep -E "^ii language-pack-[a-z]* " | |
370 | cut -d ' ' -f3) | sort -u` | |
371 | fi | |
28b62856 | 372 | packages_template="${packages_template},$(echo $langpacks | sed 's/ /,/g')" |
fade719e | 373 | |
88753f7a VK |
374 | if [ -n "$variant" ]; then |
375 | debootstrap_parameters="$debootstrap_parameters --variant=$variant" | |
376 | fi | |
f8f9b715 | 377 | if [ "$variant" = 'minbase' ]; then |
649f249c DJL |
378 | packages_template="${packages_template},sudo" |
379 | # Newer releases use netplan, EOL releases not supported | |
380 | case $release in | |
381 | trusty|xenial|zesty) | |
382 | packages_template="${packages_template},ifupdown,isc-dhcp-client" | |
383 | ;; | |
384 | esac | |
9d95ca97 | 385 | fi |
fade719e | 386 | |
28b62856 | 387 | echo "Installing packages in template: ${packages_template}" |
3f033aa8 | 388 | |
f1ccde27 | 389 | trap cleanup EXIT SIGHUP SIGINT SIGTERM |
3f033aa8 | 390 | # check the mini ubuntu was not already downloaded |
654bf1af | 391 | try_mksubvolume "$cache/partial-$arch" |
3f033aa8 | 392 | if [ $? -ne 0 ]; then |
e2b4064f SH |
393 | echo "Failed to create '$cache/partial-$arch' directory" |
394 | return 1 | |
3f033aa8 WM |
395 | fi |
396 | ||
cf0f9033 | 397 | choose_container_proxy $cache/partial-$arch/ $arch |
3f033aa8 | 398 | # download a mini ubuntu into a cache |
e2b4064f | 399 | echo "Downloading ubuntu $release minimal ..." |
5a50e09a | 400 | if [ -n "$(which qemu-debootstrap)" ]; then |
88753f7a | 401 | qemu-debootstrap --verbose $debootstrap_parameters --components=main,universe --arch=$arch --include=${packages_template} $release $cache/partial-$arch $MIRROR |
5a50e09a | 402 | else |
88753f7a | 403 | debootstrap --verbose $debootstrap_parameters --components=main,universe --arch=$arch --include=${packages_template} $release $cache/partial-$arch $MIRROR |
5a50e09a SH |
404 | fi |
405 | ||
3f033aa8 | 406 | if [ $? -ne 0 ]; then |
e2b4064f SH |
407 | echo "Failed to download the rootfs, aborting." |
408 | return 1 | |
3f033aa8 WM |
409 | fi |
410 | ||
2e44ed1e SH |
411 | # Serge isn't sure whether we should avoid doing this when |
412 | # $release == `distro-info -d` | |
413 | echo "Installing updates" | |
5a50e09a SH |
414 | > $cache/partial-$arch/etc/apt/sources.list |
415 | write_sourceslist $cache/partial-$arch/ $arch | |
416 | ||
2e44ed1e SH |
417 | chroot "$1/partial-${arch}" apt-get update |
418 | if [ $? -ne 0 ]; then | |
419 | echo "Failed to update the apt cache" | |
420 | return 1 | |
421 | fi | |
422 | cat > "$1/partial-${arch}"/usr/sbin/policy-rc.d << EOF | |
423 | #!/bin/sh | |
424 | exit 101 | |
425 | EOF | |
426 | chmod +x "$1/partial-${arch}"/usr/sbin/policy-rc.d | |
427 | ||
e34466fe SG |
428 | ( |
429 | cat << EOF | |
430 | mount -t proc proc "${1}/partial-${arch}/proc" | |
431 | chroot "${1}/partial-${arch}" apt-get dist-upgrade -y | |
432 | EOF | |
433 | ) | lxc-unshare -s MOUNT -- sh -eu || (suggest_flush; false) | |
434 | ||
2e44ed1e SH |
435 | rm -f "$1/partial-${arch}"/usr/sbin/policy-rc.d |
436 | ||
06f5c632 SH |
437 | chroot "$1/partial-${arch}" apt-get clean |
438 | ||
15da01b3 | 439 | mv "$1/partial-$arch" "$1/rootfs-$arch" |
f1ccde27 SH |
440 | trap EXIT |
441 | trap SIGINT | |
442 | trap SIGTERM | |
443 | trap SIGHUP | |
15da01b3 | 444 | echo "Download complete" |
3f033aa8 WM |
445 | return 0 |
446 | } | |
447 | ||
448 | copy_ubuntu() | |
449 | { | |
450 | cache=$1 | |
451 | arch=$2 | |
452 | rootfs=$3 | |
453 | ||
454 | # make a local copy of the miniubuntu | |
f6144f0c | 455 | echo "Copying rootfs to $rootfs ..." |
654bf1af | 456 | try_mksubvolume $rootfs |
206a255e | 457 | if which btrfs >/dev/null 2>&1 && is_btrfs_subvolume $cache/rootfs-$arch && is_btrfs_subvolume $rootfs; then |
654bf1af | 458 | realrootfs=$(dirname $config)/rootfs |
b6e07af7 | 459 | [ "$rootfs" = "$realrootfs" ] || umount $rootfs || return 1 |
654bf1af JM |
460 | btrfs subvolume delete $realrootfs || return 1 |
461 | btrfs subvolume snapshot $cache/rootfs-$arch $realrootfs || return 1 | |
b6e07af7 | 462 | [ "$rootfs" = "$realrootfs" ] || mount --bind $realrootfs $rootfs || return 1 |
654bf1af | 463 | else |
6273aef1 | 464 | rsync -SHaAX $cache/rootfs-$arch/ $rootfs/ || return 1 |
654bf1af | 465 | fi |
3f033aa8 WM |
466 | return 0 |
467 | } | |
468 | ||
469 | install_ubuntu() | |
470 | { | |
3f033aa8 | 471 | rootfs=$1 |
e2b4064f | 472 | release=$2 |
bb59e078 | 473 | flushcache=$3 |
6dc6f80b | 474 | cache="$4/$release" |
703d065d | 475 | mkdir -p $LOCALSTATEDIR/lock/subsys/ |
adca8543 | 476 | |
3f033aa8 | 477 | ( |
17abf278 | 478 | flock -x 9 |
adca8543 SH |
479 | if [ $? -ne 0 ]; then |
480 | echo "Cache repository is busy." | |
481 | return 1 | |
482 | fi | |
3f033aa8 | 483 | |
3f033aa8 | 484 | |
adca8543 SH |
485 | if [ $flushcache -eq 1 ]; then |
486 | echo "Flushing cache..." | |
654bf1af JM |
487 | try_rmsubvolume $cache/partial-$arch |
488 | try_rmsubvolume $cache/rootfs-$arch | |
adca8543 | 489 | fi |
bb59e078 | 490 | |
adca8543 SH |
491 | echo "Checking cache download in $cache/rootfs-$arch ... " |
492 | if [ ! -e "$cache/rootfs-$arch" ]; then | |
28b62856 | 493 | download_ubuntu $cache $arch $release |
adca8543 SH |
494 | if [ $? -ne 0 ]; then |
495 | echo "Failed to download 'ubuntu $release base'" | |
496 | return 1 | |
497 | fi | |
498 | fi | |
3f033aa8 | 499 | |
adca8543 SH |
500 | echo "Copy $cache/rootfs-$arch to $rootfs ... " |
501 | copy_ubuntu $cache $arch $rootfs | |
502 | if [ $? -ne 0 ]; then | |
503 | echo "Failed to copy rootfs" | |
504 | return 1 | |
505 | fi | |
3f033aa8 | 506 | |
adca8543 | 507 | return 0 |
3f033aa8 | 508 | |
17abf278 | 509 | ) 9>$LOCALSTATEDIR/lock/subsys/lxc-ubuntu$release |
3f033aa8 WM |
510 | |
511 | return $? | |
512 | } | |
513 | ||
514 | copy_configuration() | |
515 | { | |
516 | path=$1 | |
517 | rootfs=$2 | |
518 | name=$3 | |
e2b4064f | 519 | arch=$4 |
f6144f0c | 520 | release=$5 |
e2b4064f SH |
521 | |
522 | if [ $arch = "i386" ]; then | |
523 | arch="i686" | |
524 | fi | |
3f033aa8 | 525 | |
4759162d SH |
526 | # if there is exactly one veth network entry, make sure it has an |
527 | # associated hwaddr. | |
7fa3f2e9 | 528 | nics=`grep -e '^lxc\.net\.0\.type[ \t]*=[ \t]*veth' $path/config | wc -l` |
4759162d | 529 | if [ $nics -eq 1 ]; then |
7fa3f2e9 | 530 | grep -q "^lxc.net.0.hwaddr" $path/config || sed -i -e "/^lxc\.net\.0\.type[ \t]*=[ \t]*veth/a lxc.net.0.hwaddr = 00:16:3e:$(openssl rand -hex 3| sed 's/\(..\)/\1:/g; s/.$//')" $path/config |
4759162d SH |
531 | fi |
532 | ||
f2a95ee1 | 533 | # Generate the configuration file |
f2a95ee1 | 534 | ## Relocate all the network config entries |
7fa3f2e9 | 535 | sed -i -e "/lxc.net.0/{w ${path}/config-network" -e "d}" $path/config |
f2a95ee1 SG |
536 | |
537 | ## Relocate any other config entries | |
538 | sed -i -e "/lxc./{w ${path}/config-auto" -e "d}" $path/config | |
539 | ||
540 | ## Add all the includes | |
541 | echo "" >> $path/config | |
542 | echo "# Common configuration" >> $path/config | |
543 | if [ -e "${LXC_TEMPLATE_CONFIG}/ubuntu.common.conf" ]; then | |
544 | echo "lxc.include = ${LXC_TEMPLATE_CONFIG}/ubuntu.common.conf" >> $path/config | |
545 | fi | |
546 | if [ -e "${LXC_TEMPLATE_CONFIG}/ubuntu.${release}.conf" ]; then | |
547 | echo "lxc.include = ${LXC_TEMPLATE_CONFIG}/ubuntu.${release}.conf" >> $path/config | |
548 | fi | |
549 | ||
550 | ## Add the container-specific config | |
551 | echo "" >> $path/config | |
552 | echo "# Container specific configuration" >> $path/config | |
553 | [ -e "$path/config-auto" ] && cat $path/config-auto >> $path/config && rm $path/config-auto | |
7a96a068 | 554 | grep -q "^lxc.rootfs.path" $path/config 2>/dev/null || echo "lxc.rootfs.path = $rootfs" >> $path/config |
3a3ba44a | 555 | cat <<EOF >> $path/config |
b67771bc | 556 | lxc.uts.name = $name |
e2b4064f | 557 | lxc.arch = $arch |
3f033aa8 WM |
558 | EOF |
559 | ||
f2a95ee1 SG |
560 | ## Re-add the previously removed network config |
561 | echo "" >> $path/config | |
562 | echo "# Network configuration" >> $path/config | |
563 | cat $path/config-network >> $path/config | |
564 | rm $path/config-network | |
3f033aa8 WM |
565 | |
566 | if [ $? -ne 0 ]; then | |
adca8543 SH |
567 | echo "Failed to add configuration" |
568 | return 1 | |
3f033aa8 WM |
569 | fi |
570 | ||
571 | return 0 | |
572 | } | |
573 | ||
e2b4064f SH |
574 | post_process() |
575 | { | |
576 | rootfs=$1 | |
577 | release=$2 | |
28b62856 | 578 | packages=$3 |
e2b4064f | 579 | |
7ed86e44 SG |
580 | # Disable service startup |
581 | cat > $rootfs/usr/sbin/policy-rc.d << EOF | |
582 | #!/bin/sh | |
583 | exit 101 | |
584 | EOF | |
585 | chmod +x $rootfs/usr/sbin/policy-rc.d | |
586 | ||
5a50e09a SH |
587 | # If the container isn't running a native architecture, setup multiarch |
588 | if [ -x "$(ls -1 ${rootfs}/usr/bin/qemu-*-static 2>/dev/null)" ]; then | |
40f6ee00 SH |
589 | dpkg_version=$(chroot $rootfs dpkg-query -W -f='${Version}' dpkg) |
590 | if chroot $rootfs dpkg --compare-versions $dpkg_version ge "1.16.2"; then | |
591 | chroot $rootfs dpkg --add-architecture ${hostarch} | |
592 | else | |
593 | mkdir -p ${rootfs}/etc/dpkg/dpkg.cfg.d | |
594 | echo "foreign-architecture ${hostarch}" > ${rootfs}/etc/dpkg/dpkg.cfg.d/lxc-multiarch | |
595 | fi | |
5a50e09a SH |
596 | |
597 | # Save existing value of MIRROR and SECURITY_MIRROR | |
598 | DEFAULT_MIRROR=$MIRROR | |
599 | DEFAULT_SECURITY_MIRROR=$SECURITY_MIRROR | |
600 | ||
601 | # Write a new sources.list containing both native and multiarch entries | |
602 | > ${rootfs}/etc/apt/sources.list | |
603 | write_sourceslist $rootfs $arch "native" | |
604 | ||
605 | MIRROR=$DEFAULT_MIRROR | |
606 | SECURITY_MIRROR=$DEFAULT_SECURITY_MIRROR | |
607 | write_sourceslist $rootfs $hostarch "multiarch" | |
608 | ||
609 | # Finally update the lists and install upstart using the host architecture | |
d08c3aae | 610 | HOST_PACKAGES="upstart:${hostarch} mountall:${hostarch} isc-dhcp-client:${hostarch}" |
5a50e09a | 611 | chroot $rootfs apt-get update |
d08c3aae SG |
612 | if chroot $rootfs dpkg -l iproute2 | grep -q ^ii; then |
613 | HOST_PACKAGES="$HOST_PACKAGES iproute2:${hostarch}" | |
614 | else | |
615 | HOST_PACKAGES="$HOST_PACKAGES iproute:${hostarch}" | |
616 | fi | |
28b62856 GL |
617 | install_packages $rootfs $HOST_PACKAGES |
618 | fi | |
619 | ||
620 | # Install Packages in container | |
d2305c4c | 621 | if [ -n "$packages" ] |
28b62856 GL |
622 | then |
623 | local packages="`echo $packages | sed 's/,/ /g'`" | |
624 | echo "Installing packages: ${packages}" | |
625 | install_packages $rootfs $packages | |
5a50e09a | 626 | fi |
42ff5f0f | 627 | |
0a3673e8 SG |
628 | # Set initial timezone as on host |
629 | if [ -f /etc/timezone ]; then | |
630 | cat /etc/timezone > $rootfs/etc/timezone | |
631 | chroot $rootfs dpkg-reconfigure -f noninteractive tzdata | |
632 | elif [ -f /etc/sysconfig/clock ]; then | |
17abf278 | 633 | . /etc/sysconfig/clock |
0a3673e8 SG |
634 | echo $ZONE > $rootfs/etc/timezone |
635 | chroot $rootfs dpkg-reconfigure -f noninteractive tzdata | |
636 | else | |
637 | echo "Timezone in container is not configured. Adjust it manually." | |
638 | fi | |
639 | ||
adca8543 | 640 | # rmdir /dev/shm for containers that have /run/shm |
42ff5f0f SH |
641 | # I'm afraid of doing rm -rf $rootfs/dev/shm, in case it did |
642 | # get bind mounted to the host's /run/shm. So try to rmdir | |
643 | # it, and in case that fails move it out of the way. | |
5ff33774 | 644 | # NOTE: This can only be removed once 12.04 goes out of support |
68c36a30 | 645 | if [ ! -L $rootfs/dev/shm ] && [ -e $rootfs/dev/shm ]; then |
5ff33774 | 646 | rmdir $rootfs/dev/shm 2>/dev/null || mv $rootfs/dev/shm $rootfs/dev/shm.bak |
42ff5f0f SH |
647 | ln -s /run/shm $rootfs/dev/shm |
648 | fi | |
7ed86e44 SG |
649 | |
650 | # Re-enable service startup | |
651 | rm $rootfs/usr/sbin/policy-rc.d | |
e2b4064f SH |
652 | } |
653 | ||
654 | do_bindhome() | |
655 | { | |
656 | rootfs=$1 | |
657 | user=$2 | |
658 | ||
e2b4064f | 659 | # copy /etc/passwd, /etc/shadow, and /etc/group entries into container |
39aa5856 SH |
660 | pwd=`getent passwd $user` || { echo "Failed to copy password entry for $user"; false; } |
661 | echo $pwd >> $rootfs/etc/passwd | |
662 | ||
663 | # make sure user's shell exists in the container | |
664 | shell=`echo $pwd | cut -d: -f 7` | |
665 | if [ ! -x $rootfs/$shell ]; then | |
666 | echo "shell $shell for user $user was not found in the container." | |
667 | pkg=`dpkg -S $(readlink -m $shell) | cut -d ':' -f1` | |
668 | echo "Installing $pkg" | |
28b62856 | 669 | install_packages $rootfs $pkg |
e2b4064f | 670 | fi |
39aa5856 | 671 | |
e2b4064f | 672 | shad=`getent shadow $user` |
39aa5856 | 673 | echo "$shad" >> $rootfs/etc/shadow |
8565ea1c SH |
674 | |
675 | # bind-mount the user's path into the container's /home | |
676 | h=`getent passwd $user | cut -d: -f 6` | |
677 | mkdir -p $rootfs/$h | |
adca8543 SH |
678 | |
679 | # use relative path in container | |
680 | h2=${h#/} | |
681 | while [ ${h2:0:1} = "/" ]; do | |
682 | h2=${h2#/} | |
683 | done | |
f24a52d5 | 684 | echo "lxc.mount.entry = $h $h2 none bind 0 0" >> $path/config |
39aa5856 SH |
685 | |
686 | # Make sure the group exists in container | |
9db1aba4 SH |
687 | grp=`echo $pwd | cut -d: -f 4` # group number for $user |
688 | grpe=`getent group $grp` || return 0 # if host doesn't define grp, ignore in container | |
689 | chroot $rootfs getent group "$grpe" || echo "$grpe" >> $rootfs/etc/group | |
e2b4064f SH |
690 | } |
691 | ||
3f033aa8 WM |
692 | usage() |
693 | { | |
694 | cat <<EOF | |
2ef89d56 | 695 | $1 -h|--help [-a|--arch] [-b|--bindhome <user>] [-d|--debug] |
9d95ca97 | 696 | [-F | --flush-cache] [-r|--release <release>] [-v|--variant] [ -S | --auth-key <keyfile>] |
2004e7da | 697 | [--rootfs <rootfs>] [--packages <packages>] [-u|--user <user>] [--password <password>] |
04cda6d1 | 698 | [--mirror <url>] [--security-mirror <url>] |
ecb5208b | 699 | release: the ubuntu release (e.g. xenial): defaults to host release on ubuntu, otherwise uses latest LTS |
9d95ca97 | 700 | variant: debootstrap variant to use (see debootstrap(8)) |
e2b4064f | 701 | bindhome: bind <user>'s home into the container |
ce5dbd82 | 702 | The ubuntu user will not be created, and <user> will have |
adca8543 SH |
703 | sudo access. |
704 | arch: the container architecture (e.g. amd64): defaults to host arch | |
52c8f624 | 705 | auth-key: SSH Public key file to inject into container |
2004e7da | 706 | packages: list of packages to add comma separated |
04cda6d1 | 707 | mirror,security-mirror: mirror for download and /etc/apt/sources.list |
3f033aa8 WM |
708 | EOF |
709 | return 0 | |
710 | } | |
711 | ||
9d95ca97 | 712 | options=$(getopt -o a:b:hp:r:v:n:FS:du: -l arch:,bindhome:,help,path:,release:,variant:,name:,flush-cache,auth-key:,debug,rootfs:,packages:,user:,password:,mirror:,security-mirror: -- "$@") |
3f033aa8 | 713 | if [ $? -ne 0 ]; then |
bc24fe4d WM |
714 | usage $(basename $0) |
715 | exit 1 | |
3f033aa8 WM |
716 | fi |
717 | eval set -- "$options" | |
718 | ||
ecb5208b | 719 | release=xenial # Default to the last Ubuntu LTS release for non-Ubuntu systems |
c6992ecf SH |
720 | if [ -f /etc/lsb-release ]; then |
721 | . /etc/lsb-release | |
adca8543 SH |
722 | if [ "$DISTRIB_ID" = "Ubuntu" ]; then |
723 | release=$DISTRIB_CODENAME | |
724 | fi | |
c6992ecf SH |
725 | fi |
726 | ||
e2b4064f | 727 | bindhome= |
e2b4064f | 728 | |
8339b4c8 SH |
729 | # Code taken from debootstrap |
730 | if [ -x /usr/bin/dpkg ] && /usr/bin/dpkg --print-architecture >/dev/null 2>&1; then | |
731 | arch=`/usr/bin/dpkg --print-architecture` | |
169bf5e0 | 732 | elif which udpkg >/dev/null 2>&1 && udpkg --print-architecture >/dev/null 2>&1; then |
8339b4c8 SH |
733 | arch=`/usr/bin/udpkg --print-architecture` |
734 | else | |
ed4616b1 | 735 | arch=$(uname -m) |
8339b4c8 SH |
736 | if [ "$arch" = "i686" ]; then |
737 | arch="i386" | |
738 | elif [ "$arch" = "x86_64" ]; then | |
739 | arch="amd64" | |
740 | elif [ "$arch" = "armv7l" ]; then | |
8a63c0a9 | 741 | arch="armhf" |
8a3c76b2 SG |
742 | elif [ "$arch" = "aarch64" ]; then |
743 | arch="arm64" | |
744 | elif [ "$arch" = "ppc64le" ]; then | |
745 | arch="ppc64el" | |
8339b4c8 | 746 | fi |
e2b4064f SH |
747 | fi |
748 | ||
52c8f624 | 749 | debug=0 |
e2b4064f | 750 | hostarch=$arch |
bb59e078 | 751 | flushcache=0 |
d2305c4c | 752 | packages="" |
2004e7da GL |
753 | user="ubuntu" |
754 | password="ubuntu" | |
6dc6f80b | 755 | |
3f033aa8 WM |
756 | while true |
757 | do | |
758 | case "$1" in | |
e2b4064f | 759 | -h|--help) usage $0 && exit 0;; |
1897e3bc | 760 | --rootfs) rootfs=$2; shift 2;; |
e2b4064f SH |
761 | -p|--path) path=$2; shift 2;; |
762 | -n|--name) name=$2; shift 2;; | |
2004e7da GL |
763 | -u|--user) user=$2; shift 2;; |
764 | --password) password=$2; shift 2;; | |
bb59e078 | 765 | -F|--flush-cache) flushcache=1; shift 1;; |
e2b4064f | 766 | -r|--release) release=$2; shift 2;; |
9d95ca97 | 767 | -v|--variant) variant=$2; shift 2;; |
2004e7da | 768 | --packages) packages=$2; shift 2;; |
e2b4064f SH |
769 | -b|--bindhome) bindhome=$2; shift 2;; |
770 | -a|--arch) arch=$2; shift 2;; | |
52c8f624 SH |
771 | -S|--auth-key) auth_key=$2; shift 2;; |
772 | -d|--debug) debug=1; shift 1;; | |
5eb28ae4 GL |
773 | --mirror) MIRROR=$2; shift 2;; |
774 | --security-mirror) SECURITY_MIRROR=$2; shift 2;; | |
e2b4064f | 775 | --) shift 1; break ;; |
bc24fe4d | 776 | *) break ;; |
3f033aa8 WM |
777 | esac |
778 | done | |
779 | ||
52c8f624 SH |
780 | if [ $debug -eq 1 ]; then |
781 | set -x | |
782 | fi | |
783 | ||
9db1aba4 SH |
784 | if [ -n "$bindhome" ]; then |
785 | pwd=`getent passwd $bindhome` | |
786 | if [ $? -ne 0 ]; then | |
787 | echo "Error: no password entry found for $bindhome" | |
788 | exit 1 | |
789 | fi | |
8565ea1c SH |
790 | fi |
791 | ||
792 | ||
17abf278 | 793 | if [ "$arch" = "i686" ]; then |
e2b4064f SH |
794 | arch=i386 |
795 | fi | |
796 | ||
e2b4064f | 797 | if [ $hostarch = "i386" -a $arch = "amd64" ]; then |
8a63c0a9 SG |
798 | echo "can't create $arch container on $hostarch" |
799 | exit 1 | |
800 | fi | |
801 | ||
8a3c76b2 SG |
802 | if [ $hostarch = "armhf" -o $hostarch = "armel" -o $hostarch = "arm64" ] && \ |
803 | [ $arch != "armhf" -a $arch != "armel" -a $arch != "arm64" ]; then | |
804 | echo "can't create $arch container on $hostarch" | |
805 | exit 1 | |
806 | fi | |
807 | ||
808 | if [ $arch = "arm64" ] && [ $hostarch != "arm64" ]; then | |
8a63c0a9 SG |
809 | echo "can't create $arch container on $hostarch" |
810 | exit 1 | |
811 | fi | |
812 | ||
813 | if [ $hostarch = "powerpc" -a $arch != "powerpc" ]; then | |
814 | echo "can't create $arch container on $hostarch" | |
e2b4064f SH |
815 | exit 1 |
816 | fi | |
817 | ||
169bf5e0 | 818 | which debootstrap >/dev/null 2>&1 || { echo "'debootstrap' command is missing" >&2; false; } |
3f033aa8 WM |
819 | |
820 | if [ -z "$path" ]; then | |
821 | echo "'path' parameter is required" | |
822 | exit 1 | |
823 | fi | |
824 | ||
825 | if [ "$(id -u)" != "0" ]; then | |
826 | echo "This script should be run as 'root'" | |
827 | exit 1 | |
828 | fi | |
829 | ||
1881820a SH |
830 | # detect rootfs |
831 | config="$path/config" | |
1897e3bc SH |
832 | # if $rootfs exists here, it was passed in with --rootfs |
833 | if [ -z "$rootfs" ]; then | |
7a96a068 CB |
834 | if grep -q '^lxc.rootfs.path' $config 2>/dev/null ; then |
835 | rootfs=$(awk -F= '/^lxc.rootfs.path =/{ print $2 }' $config) | |
1897e3bc SH |
836 | else |
837 | rootfs=$path/rootfs | |
838 | fi | |
1881820a | 839 | fi |
3f033aa8 | 840 | |
6dc6f80b | 841 | install_ubuntu $rootfs $release $flushcache $LXC_CACHE_PATH |
3f033aa8 | 842 | if [ $? -ne 0 ]; then |
e2b4064f | 843 | echo "failed to install ubuntu $release" |
3f033aa8 WM |
844 | exit 1 |
845 | fi | |
846 | ||
2004e7da | 847 | configure_ubuntu $rootfs $name $release $user $password |
3f033aa8 | 848 | if [ $? -ne 0 ]; then |
e2b4064f | 849 | echo "failed to configure ubuntu $release for a container" |
3f033aa8 WM |
850 | exit 1 |
851 | fi | |
852 | ||
f6144f0c | 853 | copy_configuration $path $rootfs $name $arch $release |
3f033aa8 WM |
854 | if [ $? -ne 0 ]; then |
855 | echo "failed write configuration file" | |
856 | exit 1 | |
857 | fi | |
858 | ||
28b62856 | 859 | post_process $rootfs $release $trim_container $packages |
ce5dbd82 SH |
860 | |
861 | if [ -n "$bindhome" ]; then | |
862 | do_bindhome $rootfs $bindhome | |
863 | finalize_user $bindhome | |
864 | else | |
2004e7da | 865 | finalize_user $user |
e2b4064f SH |
866 | fi |
867 | ||
f6144f0c SH |
868 | echo "" |
869 | echo "##" | |
c6ed4d04 | 870 | if [ -n "$bindhome" ]; then |
1e1f8eeb | 871 | echo "# Log in as user $bindhome" |
c6ed4d04 | 872 | else |
2004e7da | 873 | echo "# The default user is '$user' with password '$password'!" |
1e1f8eeb | 874 | echo "# Use the 'sudo' command to run tasks as root in the container." |
c6ed4d04 | 875 | fi |
f6144f0c SH |
876 | echo "##" |
877 | echo "" |