]>
Commit | Line | Data |
---|---|---|
f6267d90 AV |
1 | #!/bin/bash |
2 | ||
3 | # | |
4 | # template script for generating Arch linux container for LXC | |
5 | # | |
6 | ||
7 | # | |
8 | # lxc: linux Container library | |
9 | ||
10 | # Authors: | |
11 | # Alexander Vladimirov <idkfa@vlan1.ru> | |
12 | ||
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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | |
26 | ||
27 | # defaults | |
28 | arch=$(arch) | |
29 | cache=/var/cache/lxc/arch/${arch} | |
30 | lxc_network_type="veth" | |
31 | lxc_network_link="br0" | |
32 | default_path=/var/lib/lxc | |
33 | default_rc_locale="en-US.UTF-8" | |
34 | default_rc_timezone="UTC" | |
35 | host_mirror="http://mirrors.kernel.org/archlinux/\$repo/os/$arch" | |
36 | ||
37 | # sort of minimal package set | |
38 | base_packages=( | |
39 | "filesystem" | |
40 | "initscripts" | |
41 | "coreutils" | |
42 | "module-init-tools" | |
43 | "procps" | |
44 | "psmisc" | |
45 | "pacman" | |
46 | "bash" | |
47 | "syslog-ng" | |
48 | "cronie" | |
49 | "iproute2" | |
50 | "iputils" | |
51 | "inetutils" | |
52 | "dhcpcd" | |
53 | "dnsutils" | |
54 | "nano" | |
55 | "grep" | |
56 | "less" | |
57 | "gawk" | |
58 | "sed" | |
59 | "tar" | |
60 | "wget" | |
61 | "gzip" | |
62 | "which" | |
63 | ) | |
64 | declare -a additional_packages | |
65 | ||
66 | [ -f /etc/arch-release ] && is_arch=true | |
67 | ||
68 | # find and extract parameter value from given config file | |
69 | # ${1} - file to read parameter from | |
70 | # ${2} - parameter name | |
71 | # ${result} - result value on success | |
72 | function read_parameter_value { | |
73 | [ -f ${1} ] && [ "${2}" ] || return 1 | |
74 | local pattern="^[[:space:]]*${2}[[:space:]]*=[[:space:]]*" | |
75 | local str=$(grep "${pattern}" "${1}") | |
76 | local str=${str/#$(grep -o "${pattern}" "${1}")/} | |
77 | result=${str//\"/} | |
78 | return 0 | |
79 | } | |
80 | ||
81 | # split comma-separated string into an array | |
82 | # ${1} - string to split | |
83 | # ${2} - separator (default is ",") | |
84 | # ${result} - result value on success | |
85 | function split_string { | |
86 | local ifs=${IFS} | |
87 | IFS="${2:-,}" | |
88 | read -a result < <(echo "${1}") | |
89 | IFS=${ifs} | |
90 | return 0 | |
91 | } | |
92 | ||
93 | # Arch-specific preconfiguration for container | |
94 | function configure_arch { | |
95 | # read locale and timezone defaults from system rc.conf if running on Arch | |
96 | if [ "${is_arch}" ]; then | |
97 | read_parameter_value "/etc/rc.conf" "LOCALE" | |
98 | rc_locale=${result:-${default_rc_locale}} | |
99 | read_parameter_value "/etc/rc.conf" "TIMEZONE" | |
100 | rc_timezone=${result:-${default_rc_timezone}} | |
101 | else | |
102 | rc_locale=${default_rc_locale} | |
103 | rc_timezone=${default_rc_timezone} | |
104 | fi | |
105 | ||
106 | echo "Setting up rc.conf" | |
107 | cat > "${rootfs_path}/etc/rc.conf" << EOF | |
108 | # /etc/rc.conf - Main Configuration for Arch Linux | |
109 | LOCALE="${rc_locale}" | |
110 | DAEMON_LOCALE="no" | |
111 | HARDWARECLOCK="local" | |
112 | TIMEZONE="${rc_timezone}" | |
113 | KEYMAP=us | |
114 | CONSOLEFONT= | |
115 | CONSOLEMAP= | |
116 | USECOLOR="yes" | |
117 | MODULES=() | |
118 | HOSTNAME="${name}" | |
119 | interface=eth0 | |
120 | address= | |
121 | netmask= | |
122 | broadcast= | |
123 | gateway= | |
124 | DAEMONS=(syslog-ng crond network) | |
125 | EOF | |
126 | ||
127 | if [ -e "${rootfs_path}/etc/locale.gen" ]; then | |
128 | sed -i 's@^#\(en_US\.UTF-8\)@\1@' "${rootfs_path}/etc/locale.gen" | |
129 | if [ ! "${rc_locale}" = "en_US.UTF-8" ]; then | |
130 | echo "${rc_locale} ${rc_locale##*.}" >> "${rootfs_path}/etc/locale.gen" | |
131 | fi | |
132 | chroot "${rootfs_path}" locale-gen | |
133 | fi | |
134 | cp "${rootfs_path}/usr/share/zoneinfo/${rc_timezone}" \ | |
135 | "${rootfs_path}/etc/localtime" | |
136 | ||
137 | echo "Setting up rc.sysinit" | |
138 | cat > "${rootfs_path}/etc/rc.sysinit.lxc" << EOF | |
139 | #!/bin/bash | |
140 | . /etc/rc.conf | |
141 | . /etc/rc.d/functions | |
142 | ||
143 | echo "starting Arch Linux" | |
144 | rm -f \$(find /var/run -name '*pid') | |
145 | rm -f /run/daemons/* | |
146 | rm -f /var/lock/subsys/* | |
147 | rm -f /etc/mtab | |
148 | touch /etc/mtab | |
149 | run_hook sysinit_end | |
150 | EOF | |
151 | ||
152 | echo "Setting up rc.shutdown" | |
153 | cat > "${rootfs_path}/etc/rc.shutdown.lxc" << EOF | |
154 | #!/bin/bash | |
155 | . /etc/rc.conf | |
156 | . /etc/rc.d/functions | |
157 | stty onlcr | |
158 | run_hook shutdown_start | |
159 | [[ -x /etc/rc.local.shutdown ]] && /etc/rc.local.shutdown | |
160 | stop_all_daemons | |
161 | run_hook shutdown_prekillall | |
162 | kill_all | |
163 | run_hook shutdown_postkillall | |
164 | [[ \${TIMEZONE} ]] && cp --remove-destination "/usr/share/zoneinfo/\${TIMEZONE}" /etc/localtime | |
165 | halt -w | |
166 | umount -a -r -t nodevtmpfs,notmpfs,nosysfs,noproc,nodevpts -O no_netdev | |
167 | run_hook shutdown_postumount | |
168 | run_hook shutdown_poweroff | |
169 | if [[ \${RUNLEVEL} = 0 ]]; then | |
170 | poweroff -d -f -i | |
171 | else | |
172 | reboot -d -f -i | |
173 | fi | |
174 | # vim: set ts=2 sw=2 noet: | |
175 | EOF | |
176 | chmod 755 "${rootfs_path}/etc/rc.shutdown.lxc" "${rootfs_path}/etc/rc.sysinit.lxc" | |
177 | ||
178 | echo "Setting up inittab" | |
179 | cat > "${rootfs_path}/etc/inittab" << EOF | |
180 | id:3:initdefault: | |
181 | rc::sysinit:/etc/rc.sysinit.lxc | |
182 | rs:S1:wait:/etc/rc.single | |
183 | rm:2345:wait:/etc/rc.multi | |
184 | rh:06:wait:/etc/rc.shutdown.lxc | |
185 | su:S:wait:/sbin/sulogin -p | |
186 | c1:2345:respawn:/sbin/agetty -8 38400 tty1 linux | |
187 | EOF | |
188 | ||
189 | echo "Setting up hosts" | |
190 | cat > "${rootfs_path}/etc/hosts" << EOF | |
191 | 127.0.0.1 localhost.localdomain localhost ${name} | |
192 | ::1 localhost.localdomain localhost | |
193 | EOF | |
194 | ||
195 | echo "Setting up nameserver" | |
196 | grep nameserver /etc/resolv.conf > "${rootfs_path}/etc/resolv.conf" | |
197 | ||
198 | echo "Setting up device nodes" | |
199 | mkdir -m 755 "${rootfs_path}/dev/pts" | |
200 | mkdir -m 1777 "${rootfs_path}/dev/shm" | |
201 | mknod -m 666 "${rootfs_path}/dev/null" c 1 3 | |
202 | mknod -m 666 "${rootfs_path}/dev/full" c 1 7 | |
203 | mknod -m 666 "${rootfs_path}/dev/random" c 1 8 | |
204 | mknod -m 666 "${rootfs_path}/dev/urandom" c 1 9 | |
205 | mknod -m 666 "${rootfs_path}/dev/tty0" c 4 0 | |
206 | mknod -m 666 "${rootfs_path}/dev/tty1" c 4 1 | |
207 | mknod -m 666 "${rootfs_path}/dev/tty2" c 4 2 | |
208 | mknod -m 666 "${rootfs_path}/dev/tty3" c 4 3 | |
209 | mknod -m 666 "${rootfs_path}/dev/tty4" c 4 4 | |
210 | mknod -m 600 "${rootfs_path}/dev/initctl" p | |
211 | mknod -m 666 "${rootfs_path}/dev/tty" c 5 0 | |
212 | mknod -m 666 "${rootfs_path}/dev/console" c 5 1 | |
213 | mknod -m 666 "${rootfs_path}/dev/ptmx" c 5 2 | |
214 | ||
215 | return 0 | |
216 | } | |
217 | ||
218 | # write container configuration files | |
219 | function copy_configuration { | |
220 | mkdir -p "${config_path}" | |
221 | cat > "${config_path}/config" << EOF | |
222 | lxc.utsname=${name} | |
223 | lxc.tty=4 | |
224 | lxc.pts=1024 | |
225 | lxc.rootfs=${rootfs_path} | |
226 | lxc.mount=${config_path}/fstab | |
227 | #networking | |
228 | lxc.network.type=${lxc_network_type} | |
229 | lxc.network.flags=up | |
230 | lxc.network.link=${lxc_network_link} | |
231 | lxc.network.name=eth0 | |
232 | lxc.network.mtu=1500 | |
233 | #cgroups | |
234 | lxc.cgroup.devices.deny = a | |
235 | # /dev/null and zero | |
236 | lxc.cgroup.devices.allow = c 1:3 rwm | |
237 | lxc.cgroup.devices.allow = c 1:5 rwm | |
238 | # consoles | |
239 | lxc.cgroup.devices.allow = c 5:1 rwm | |
240 | lxc.cgroup.devices.allow = c 5:0 rwm | |
241 | lxc.cgroup.devices.allow = c 4:0 rwm | |
242 | lxc.cgroup.devices.allow = c 4:1 rwm | |
243 | # /dev/{,u}random | |
244 | lxc.cgroup.devices.allow = c 1:9 rwm | |
245 | lxc.cgroup.devices.allow = c 1:8 rwm | |
246 | # /dev/pts | |
247 | lxc.cgroup.devices.allow = c 136:* rwm | |
248 | lxc.cgroup.devices.allow = c 5:2 rwm | |
249 | # rtc | |
250 | lxc.cgroup.devices.allow = c 254:0 rwm | |
251 | EOF | |
252 | ||
253 | cat > "${config_path}/fstab" << EOF | |
254 | none ${rootfs_path}/dev/pts devpts defaults 0 0 | |
255 | none ${rootfs_path}/proc proc nodev,noexec,nosuid 0 0 | |
256 | none ${rootfs_path}/sys sysfs defaults 0 0 | |
257 | none ${rootfs_path}/dev/shm tmpfs defaults 0 0 | |
258 | EOF | |
259 | ||
260 | if [ ${?} -ne 0 ]; then | |
261 | echo "Failed to configure container" | |
262 | return 1 | |
263 | fi | |
264 | ||
265 | return 0 | |
266 | } | |
267 | ||
268 | # lock chroot and mount subdirectories before installing container | |
269 | function mount_chroot { | |
270 | echo "mounting chroot" | |
271 | umask 0022 | |
272 | [ -e "${rootfs_path}/sys" ] || mkdir "${rootfs_path}/sys" | |
273 | mount -t sysfs sysfs "${rootfs_path}/sys" | |
274 | [ -e "${rootfs_path}/proc" ] || mkdir "${rootfs_path}/proc" | |
275 | mount -t proc proc "${rootfs_path}/proc" | |
276 | [ -e "${rootfs_path}/dev" ] || mkdir "${rootfs_path}/dev" | |
277 | mount -t tmpfs dev "${rootfs_path}/dev" -o mode=0755,size=10M,nosuid | |
278 | mknod -m 666 "${rootfs_path}/dev/null" c 1 3 | |
279 | mknod -m 666 "${rootfs_path}/dev/zero" c 1 5 | |
280 | mknod -m 600 "${rootfs_path}/dev/console" c 5 1 | |
281 | mknod -m 644 "${rootfs_path}/dev/random" c 1 8 | |
282 | mknod -m 644 "${rootfs_path}/dev/urandom" c 1 9 | |
283 | mknod -m 666 "${rootfs_path}/dev/tty" c 5 0 | |
284 | mknod -m 666 "${rootfs_path}/dev/tty0" c 4 0 | |
285 | mknod -m 666 "${rootfs_path}/dev/full" c 1 7 | |
286 | ln -s /proc/kcore "${rootfs_path}/dev/core" | |
287 | ln -s /proc/self/fd "${rootfs_path}/dev/fd" | |
288 | ln -s /proc/self/fd/0 "${rootfs_path}/dev/stdin" | |
289 | ln -s /proc/self/fd/1 "${rootfs_path}/dev/stdout" | |
290 | ln -s /proc/self/fd/2 "${rootfs_path}/dev/stderr" | |
291 | [ -e "${rootfs_path}/dev/shm" ] || mkdir "${rootfs_path}/dev/shm" | |
292 | mount -t tmpfs shm "${rootfs_path}/dev/shm" -o nodev,nosuid,size=128M | |
293 | [ -e "${rootfs_path}/dev/pts" ] || mkdir "${rootfs_path}/dev/pts" | |
294 | mount -t devpts devpts "${rootfs_path}/dev/pts" -o newinstance,ptmxmode=666 | |
295 | ln -s pts/ptmx "${rootfs_path}/dev/ptmx" | |
296 | [ -e "${cache_dir}" ] || mkdir -p "${cache_dir}" | |
297 | [ -e "${rootfs_path}/${cache_dir}" ] || mkdir -p "${rootfs_path}/${cache_dir}" | |
298 | mount -o bind "${cache_dir}" "${rootfs_path}/${cache_dir}" | |
299 | if [ -n "${host_mirror_path}" ]; then | |
300 | [ -e "${rootfs_path}/${host_mirror_path}" ] || mkdir -p "${rootfs_path}/${host_mirror_path}" | |
301 | mount -o bind "${host_mirror_path}" "${rootfs_path}/${host_mirror_path}" | |
302 | mount -o remount,ro,bind "${host_mirror_path}" "${rootfs_path}/${host_mirror_path}" | |
303 | fi | |
304 | trap 'umount_chroot' EXIT INT QUIT TERM HUP | |
305 | } | |
306 | ||
307 | function umount_chroot { | |
308 | if [ -z "${umount_done}" ]; then | |
309 | echo "unmounting chroot" | |
310 | umount "${rootfs_path}/proc" | |
311 | umount "${rootfs_path}/sys" | |
312 | umount "${rootfs_path}/dev/pts" | |
313 | umount "${rootfs_path}/dev/shm" | |
314 | umount "${rootfs_path}/dev" | |
315 | umount "${rootfs_path}/${cache_dir}" | |
316 | [ -n "${host_mirror_path}" ] && umount "${rootfs_path}/${host_mirror_path}" | |
317 | umount_done=1 | |
318 | fi | |
319 | } | |
320 | ||
321 | # install packages within container chroot | |
322 | function install_arch { | |
323 | pacman_config=$(mktemp) | |
324 | ||
325 | cat <<EOF > "${pacman_config}" | |
326 | [options] | |
327 | HoldPkg = pacman glibc | |
328 | SyncFirst = pacman | |
329 | Architecture = auto | |
330 | #IgnorePkg = udev | |
331 | [core] | |
332 | Include = /etc/pacman.d/mirrorlist | |
333 | Server = ${host_mirror} | |
334 | [extra] | |
335 | Include = /etc/pacman.d/mirrorlist | |
336 | Server = ${host_mirror} | |
337 | [community] | |
338 | Include = /etc/pacman.d/mirrorlist | |
339 | Server = ${host_mirror} | |
340 | EOF | |
341 | ||
342 | mkdir -p "${rootfs_path}/var/lib/pacman/sync" | |
343 | mkdir -p "${rootfs_path}/etc" | |
344 | ||
345 | if echo "${host_mirror}" | grep -q 'file://'; then | |
346 | host_mirror_path=$(echo "${host_mirror}" | sed -E 's#file://(/.*)/\$repo/os/\$arch#\1#g') | |
347 | fi | |
348 | cache_dir=$( (grep -m 1 '^CacheDir' "${pacman_config}" || echo 'CacheDir = /var/cache/pacman/pkg') | sed 's/CacheDir\s*=\s*//') | |
349 | mount_chroot | |
350 | params="--root ${rootfs_path} --config=${pacman_config} --noconfirm" | |
351 | if ! pacman -Sydd ${params} --dbonly udev; then | |
352 | echo "Failed to preinstall udev package record" | |
353 | return 1 | |
354 | fi | |
355 | if ! pacman -S ${params} ${base_packages[@]}; then | |
356 | echo "Failed to install container packages" | |
357 | return 1 | |
358 | fi | |
359 | [ -d "${rootfs_path}/lib/modules" ] && ldconfig -r "${rootfs_path}" | |
360 | mv "${pacman_config}" "${rootfs_path}/etc/pacman.conf" | |
361 | umount_chroot | |
362 | return 0 | |
363 | } | |
364 | ||
365 | usage() | |
366 | { | |
367 | cat <<EOF | |
368 | usage: | |
369 | ${1} -n|--name=<container_name> | |
370 | [-P|--packages=<pkg1,pkg2,...>] [-p|--path=<path>] [-h|--help] | |
371 | Mandatory args: | |
372 | -n,--name container name, used to as an identifier for that container from now on | |
373 | Optional args: | |
374 | -p,--path path to where the container rootfs will be created, defaults to /var/lib/lxc. The container config will go under /var/lib/lxc in that case | |
375 | -P,--packages preinstall additional packages, comma-separated list | |
376 | -h,--help print this help | |
377 | EOF | |
378 | return 0 | |
379 | } | |
380 | ||
381 | options=$(getopt -o hp:P:n:cm: -l help,path:,packages:,name:,clean,mirror: -- "${@}") | |
382 | if [ ${?} -ne 0 ]; then | |
383 | usage $(basename ${0}) | |
384 | exit 1 | |
385 | fi | |
386 | eval set -- "${options}" | |
387 | ||
388 | while true | |
389 | do | |
390 | case "${1}" in | |
391 | -h|--help) usage ${0} && exit 0;; | |
392 | -p|--path) path=${2}; shift 2;; | |
393 | -n|--name) name=${2}; shift 2;; | |
394 | -P|--packages) additional_packages=${2}; shift 2;; | |
395 | -m|--mirror) host_mirror=${2}; shift 2;; | |
396 | --) shift 1; break ;; | |
397 | *) break ;; | |
398 | esac | |
399 | done | |
400 | ||
401 | if [ -z "${name}" ]; then | |
402 | echo "missing required 'name' parameter" | |
403 | exit 1 | |
404 | fi | |
405 | ||
406 | type pacman >/dev/null 2>&1 | |
407 | if [ ${?} -ne 0 ]; then | |
408 | echo "'pacman' command is missing, refer to wiki.archlinux.org for information about installing pacman" | |
409 | exit 1 | |
410 | fi | |
411 | ||
412 | if [ -z "${path}" ]; then | |
413 | path="${default_path}/${name}" | |
414 | fi | |
415 | ||
416 | if [ "${EUID}" != "0" ]; then | |
417 | echo "This script should be run as 'root'" | |
418 | exit 1 | |
419 | fi | |
420 | ||
421 | rootfs_path="${path}/rootfs" | |
422 | config_path="${default_path}/${name}" | |
423 | ||
424 | revert() | |
425 | { | |
426 | echo "Interrupted, so cleaning up" | |
427 | lxc-destroy -n "${name}" | |
428 | # maybe was interrupted before copy config | |
429 | rm -rf "${path}/${name}" | |
430 | rm -rf "${default_path}/${name}" | |
431 | exit 1 | |
432 | } | |
433 | ||
434 | trap revert SIGHUP SIGINT SIGTERM | |
435 | ||
436 | copy_configuration | |
437 | if [ ${?} -ne 0 ]; then | |
438 | echo "failed write configuration file" | |
439 | rm -rf "${config_path}" | |
440 | exit 1 | |
441 | fi | |
442 | ||
443 | if [ ${#additional_packages[@]} -gt 0 ]; then | |
444 | split_string ${additional_packages} | |
445 | base_packages+=(${result[@]}) | |
446 | fi | |
447 | ||
448 | install_arch | |
449 | if [ ${?} -ne 0 ]; then | |
450 | echo "failed to install Arch linux" | |
451 | rm -rf "${config_path}" "${path}" | |
452 | exit 1 | |
453 | fi | |
454 | ||
455 | configure_arch | |
456 | if [ ${?} -ne 0 ]; then | |
457 | echo "failed to configure Arch linux for a container" | |
458 | rm -rf "${config_path}" "${path}" | |
459 | exit 1 | |
460 | fi | |
461 | ||
462 | echo "container rootfs and config created" |