1 #!/bin/bash 2 3 # 4 # template script for generating centos container for LXC 5 # 6 7 # 8 # lxc: linux Container library 9 10 # Authors: 11 # Johannes Graumann <johannes_graumann@web.de> 12 # Based on the lxc-fedora template by 13 # Daniel Lezcano <daniel.lezcano@free.fr> 14 # Ramez Hanna <rhanna@informatiq.org> 15 16 # This library is free software; you can redistribute it and/or 17 # modify it under the terms of the GNU Lesser General Public 18 # License as published by the Free Software Foundation; either 19 # version 2.1 of the License, or (at your option) any later version. 20 21 # This library is distributed in the hope that it will be useful, 22 # but WITHOUT ANY WARRANTY; without even the implied warranty of 23 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 24 # Lesser General Public License for more details. 25 26 # You should have received a copy of the GNU Lesser General Public 27 # License along with this library; if not, write to the Free Software 28 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 29 30 31 #Configurations 32 arch=$(arch) 33 cache_base=/var/cache/lxc/centos/$arch 34 default_path=/var/lib/lxc 35 root_password=root 36 37 # is this centos? 38 [ -f /etc/centos-release ] && is_centos=true 39 40 if [ "$arch" = "i686" ]; then 41 basearch='i386' 42 else 43 basearch="${arch}" 44 fi 45 46 configure_centos() 47 { 48 49 # disable selinux in centos 50 mkdir -p $rootfs_path/selinux 51 echo 0 > $rootfs_path/selinux/enforce 52 53 # configure the network using the dhcp 54 cat <<EOF > ${rootfs_path}/etc/sysconfig/network-scripts/ifcfg-eth0 55 DEVICE=eth0 56 BOOTPROTO=dhcp 57 ONBOOT=yes 58 HOSTNAME=${name} 59 NM_CONTROLLED=no 60 TYPE=Ethernet 61 EOF 62 63 # set the hostname 64 cat <<EOF > ${rootfs_path}/etc/sysconfig/network 65 NETWORKING=yes 66 HOSTNAME=${name} 67 EOF 68 69 # set minimal hosts 70 cat <<EOF > $rootfs_path/etc/hosts 71 127.0.0.1 localhost $name 72 EOF 73 74 sed -i 's|.sbin.start_udev||' ${rootfs_path}/etc/rc.sysinit 75 sed -i 's|.sbin.start_udev||' ${rootfs_path}/etc/rc.d/rc.sysinit 76 chroot ${rootfs_path} chkconfig udev-post off 77 chroot ${rootfs_path} chkconfig network on 78 79 dev_path="${rootfs_path}/dev" 80 rm -rf $dev_path 81 mkdir -p $dev_path 82 mknod -m 666 ${dev_path}/null c 1 3 83 mknod -m 666 ${dev_path}/zero c 1 5 84 mknod -m 666 ${dev_path}/random c 1 8 85 mknod -m 666 ${dev_path}/urandom c 1 9 86 mkdir -m 755 ${dev_path}/pts 87 mkdir -m 1777 ${dev_path}/shm 88 mknod -m 666 ${dev_path}/tty c 5 0 89 mknod -m 666 ${dev_path}/tty0 c 4 0 90 mknod -m 666 ${dev_path}/tty1 c 4 1 91 mknod -m 666 ${dev_path}/tty2 c 4 2 92 mknod -m 666 ${dev_path}/tty3 c 4 3 93 mknod -m 666 ${dev_path}/tty4 c 4 4 94 mknod -m 600 ${dev_path}/console c 5 1 95 mknod -m 666 ${dev_path}/full c 1 7 96 mknod -m 600 ${dev_path}/initctl p 97 mknod -m 666 ${dev_path}/ptmx c 5 2 98 99 echo "setting root passwd to $root_password" 100 echo "root:$root_password" | chroot $rootfs_path chpasswd 101 102 return 0 103 } 104 105 function rdom () { 106 local IFS='>' 107 read -d '<' E C 108 } 109 110 function yum_init() { 111 local url 112 local repomd xmldata 113 114 echo 'Initializing yum database' >&2 115 116 url="$1" 117 118 repomd="$(wget -O - -o /dev/null "${url}/repodata/repomd.xml")" 119 120 xmldata_path="`while rdom; do 121 case "${E}" in 122 *location\ href=*-primary.xml.gz*) 123 echo "$E" | sed 's@^.* href="@@;s@".*$@@' 124 ;; 125 esac 126 done <<<"${repomd}"`" 127 128 xmldata="$(wget -O - "${url}/${xmldata_path}" | gzip -dc)" 129 130 echo 'function yum_download() {' 131 echo ' local package download_dir' 132 echo '' 133 echo ' package="$1"' 134 echo ' download_dir="$2"' 135 echo '' 136 echo ' case "${package}" in' 137 138 echo 'Reading package information:' >&2 139 140 while rdom; do 141 142 obj="${E%% *}" 143 attrs="${E#* }" 144 145 is_closing='0' 146 is_opening='0' 147 case "${obj}" in 148 ''|'?'*) 149 continue 150 ;; 151 /*) 152 obj="${obj#/}" 153 154 is_closing='1' 155 ;; 156 *) 157 is_opening='1' 158 case "${E}" in 159 */) 160 obj="${obj%/}" 161 attrs="${attrs%/}" 162 # Self closing 163 is_closing='1' 164 ;; 165 esac 166 167 state="${state}/${obj}" 168 169 ;; 170 esac 171 172 case "${state}" in 173 /metadata) 174 totalpackages="$(echo "${attrs}" | sed 's@^.* packages="@@;s@".*$@@')" 175 ;; 176 /metadata/package) 177 if [ "${is_closing}" = '1' ]; then 178 packageidx=$[${packageidx} + 1] 179 180 echo -ne " * ${packageidx} / ${totalpackages} "'\r' >&2 181 182 # Emit package data 183 provides_case="$(echo "${provides}" | sed 's@ *@ @g;s@^ *@'"'"'@;s@ *$@'"'"'@;s@ @'"'|'"'@g')" 184 package_file="$(basename "${location}")" 185 186 echo " ${provides_case})" 187 echo " if [ -f \"\${download_dir}\"'/${package_file}' ]; then" 188 echo ' echo "Skipping ${package}"' 189 echo '' 190 echo " return 0" 191 echo " fi" 192 echo '' 193 echo ' echo "Installing ${package}"' 194 echo "" 195 echo " wget -o /dev/null -O \"\${download_dir}\"'/${package_file}' '${url}/${location}' || rm -f \"\${download_dir}\"'/${package_file}'" 196 echo "" 197 echo " if [ ! -f \"\${download_dir}\"'/${package_file}' ]; then" 198 echo " return 1" 199 echo " fi" 200 echo "" 201 for require in ${requires}; do 202 case "${require}" in 203 /*) 204 echo " yum_download '${require}' \"\${download_dir}\" || return 1" 205 ;; 206 *) 207 if [ -n "${arch}" ]; then 208 require_with_arch="${require}.${arch}" 209 210 echo -n " yum_download '${require_with_arch}' \"\${download_dir}\" || " 211 echo "yum_download '${require}' \"\${download_dir}\" || return 1" 212 else 213 echo -n " " 214 for dotarch in '' '.x86_64' '.i686' '.i386'; do 215 echo -n "yum_download '${require}${dotarch}' \"\${download_dir}\" || " 216 done 217 echo "return 1" 218 fi 219 ;; 220 esac 221 222 done | sort -u 223 echo " ;;" 224 fi 225 226 if [ "${is_opening}" = '1' ]; then 227 # Initialize package data 228 location='' 229 provides='' 230 requires='' 231 arch='' 232 fi 233 ;; 234 /metadata/package/location) 235 location="$(eval "${attrs}"; echo "${href}")" 236 ;; 237 /metadata/package/arch) 238 if [ "${is_opening}" = '1' ]; then 239 arch="$(echo ${C})" 240 241 if [ "${arch}" = 'noarch' ]; then 242 arch='' 243 fi 244 fi 245 ;; 246 /metadata/package/format/file) 247 if [ "${is_opening}" = '1' ]; then 248 provides="${provides} $(echo ${C})" 249 fi 250 ;; 251 /metadata/package/format/rpm:provides/rpm:entry) 252 provided="$(eval "${attrs}"; echo "${name}")" 253 if [ -n "${arch}" ]; then 254 provided="${provided}.${arch}" 255 fi 256 257 provides="${provides} ${provided}" 258 ;; 259 /metadata/package/format/rpm:requires/rpm:entry) 260 requires="${requires} $(eval "${attrs}"; echo "${name}")" 261 ;; 262 *) 263 if false && [ "${is_closing}" = '1' ]; then 264 echo "${state}: ${attrs}" 265 fi 266 ;; 267 esac 268 269 if [ "${is_closing}" = '1' ]; then 270 state="${state%/*}" 271 fi 272 done <<<"${xmldata}" 273 274 echo ' *)' 275 echo ' echo "Unknown package: ${package}"' 276 echo ' return 1' 277 echo ' ;;' 278 echo ' esac' 279 echo '}' 280 281 echo '' >&2 282 } 283 284 function yum_download() { 285 local packages dir url 286 287 packages="$1" 288 dir="$2" 289 url="$3" 290 291 if [ -z "${yum_init}" ]; then 292 yum_init="$(yum_init "${url}")" 293 fi 294 295 ( 296 echo "${yum_init}" 297 for package in ${packages}; do 298 echo "yum_download '${package}' '${dir}'" 299 done 300 ) | bash 301 } 302 303 function yum_uninit() { 304 unset yum_init 305 } 306 307 download_centos() 308 { 309 local retval 310 311 # check the mini centos was not already downloaded 312 INSTALL_ROOT=$cache/partial 313 mkdir -p $INSTALL_ROOT 314 if [ $? -ne 0 ]; then 315 echo "Failed to create '$INSTALL_ROOT' directory" 316 return 1 317 fi 318 319 # download a mini centos into a cache 320 mkdir -p $INSTALL_ROOT/var/lib/rpm 321 rpm --root $INSTALL_ROOT --initdb 322 323 echo "Downloading centos minimal ..." 324 PKG_LIST="yum initscripts passwd rsyslog vim-minimal dhclient chkconfig rootfiles policycoreutils" 325 #MIRRORLIST_URL="http://mirrors.fedoraproject.org/mirrorlist?repo=fedora-$release&arch=$arch" 326 MIRRORLIST_URL="http://mirrorlist.centos.org/?release=$release&arch=$basearch&repo=os" 327 328 for trynumber in 1 2 3; do 329 [ $trynumber != 1 ] && echo "Trying again..." 330 MIRROR_URL=$(curl -s -S -f "$MIRRORLIST_URL" | head -n2 | tail -n1) 331 if [ $? -ne 0 ] || [ -z "$MIRROR_URL" ]; then 332 echo "Failed to get a mirror" 333 continue 334 fi 335 #RELEASE_URL="$MIRROR_URL/Packages/fedora-release-$release-1.noarch.rpm" 336 337 DOWNLOAD_OK='yes' 338 for package in centos-release-$release-3.el6.centos.9.$arch.rpm; do 339 if [ -s "${INSTALL_ROOT}/${package}" ]; then 340 continue 341 fi 342 343 PACKAGE_URL="$MIRROR_URL/Packages/${package}" 344 echo "Fetching from $PACKAGE_URL" 345 curl -f "$PACKAGE_URL" > "$INSTALL_ROOT/${package}" 346 347 if [ $? -ne 0 ]; then 348 rm -f "${INSTALL_ROOT}/${package}" 349 350 echo "Failed to download centos release rpm" 351 352 DOWNLOAD_OK='no' 353 354 continue 355 fi 356 357 done 358 done 359 if [ $DOWNLOAD_OK != yes ]; then 360 echo "Aborting" 361 return 1 362 fi 363 364 if [ "${yum_lite}" = '1' ]; then 365 yum_download yum "${INSTALL_ROOT}" "${MIRROR_URL}" 366 yum_uninit 367 fi 368 369 rpm --root $INSTALL_ROOT --nodeps -ivh "$INSTALL_ROOT"/*.rpm || return 1 370 rm -f "$INSTALL_ROOT"/*.rpm 371 372 if [ "${yum_lite}" = '1' ]; then 373 mount --bind /dev "${INSTALL_ROOT}/dev" 374 mount --bind /proc "${INSTALL_ROOT}/proc" 375 mount --bind /sys "${INSTALL_ROOT}/sys" 376 377 cp /etc/resolv.conf "${INSTALL_ROOT}/etc/" 378 379 chroot "${INSTALL_ROOT}" rpm --rebuilddb 380 chroot "${INSTALL_ROOT}" yum -y --nogpgcheck install $PKG_LIST 381 retval="$?" 382 383 umount "${INSTALL_ROOT}/dev" 384 umount "${INSTALL_ROOT}/proc" 385 umount "${INSTALL_ROOT}/sys" 386 else 387 yum --installroot "$INSTALL_ROOT" -y --nogpgcheck install $PKG_LIST 388 retval="$?" 389 fi 390 391 if [ "${retval}" -ne 0 ]; then 392 echo "Failed to download the rootfs, aborting." 393 return 1 394 fi 395 396 mv "$INSTALL_ROOT" "$cache/rootfs" 397 echo "Download complete." 398 399 return 0 400 } 401 402 copy_centos() 403 { 404 405 # make a local copy of the minicentos 406 echo -n "Copying rootfs to $rootfs_path ..." 407 cp -a $cache/rootfs $rootfs_path || return 1 408 return 0 409 } 410 411 update_centos() 412 { 413 cp /etc/resolv.conf $cache/rootfs/etc/resolv.conf 414 mount --bind /dev "${cache}/dev" 415 mount --bind /sys "${cache}/sys" 416 mount --bind /proc "${cache}/proc" 417 chroot $cache/rootfs yum -y update 418 umount "${cache}/dev" 419 umount "${cache}/sys" 420 umount "${cache}/proc" 421 } 422 423 install_centos() 424 { 425 mkdir -p /var/lock/subsys/ 426 ( 427 flock -n -x 200 428 if [ $? -ne 0 ]; then 429 echo "Cache repository is busy." 430 return 1 431 fi 432 433 echo "Checking cache download in $cache/rootfs ... " 434 if [ ! -e "$cache/rootfs" ]; then 435 download_centos 436 if [ $? -ne 0 ]; then 437 echo "Failed to download 'centos base'" 438 return 1 439 fi 440 else 441 echo "Cache found. Updating..." 442 update_centos 443 if [ $? -ne 0 ]; then 444 echo "Failed to update 'centos base', continuing with last known good cache" 445 else 446 echo "Update finished" 447 fi 448 fi 449 450 echo "Copy $cache/rootfs to $rootfs_path ... " 451 copy_centos 452 if [ $? -ne 0 ]; then 453 echo "Failed to copy rootfs" 454 return 1 455 fi 456 457 return 0 458 459 ) 200>/var/lock/subsys/lxc 460 461 return $? 462 } 463 464 copy_configuration() 465 { 466 467 mkdir -p $config_path 468 cat <<EOF >> $config_path/config 469 lxc.utsname = $name 470 lxc.tty = 4 471 lxc.pts = 1024 472 lxc.rootfs = $rootfs_path 473 lxc.mount = $config_path/fstab 474 #cgroups 475 lxc.cgroup.devices.deny = a 476 # /dev/null and zero 477 lxc.cgroup.devices.allow = c 1:3 rwm 478 lxc.cgroup.devices.allow = c 1:5 rwm 479 # consoles 480 lxc.cgroup.devices.allow = c 5:1 rwm 481 lxc.cgroup.devices.allow = c 5:0 rwm 482 lxc.cgroup.devices.allow = c 4:0 rwm 483 lxc.cgroup.devices.allow = c 4:1 rwm 484 # /dev/{,u}random 485 lxc.cgroup.devices.allow = c 1:9 rwm 486 lxc.cgroup.devices.allow = c 1:8 rwm 487 lxc.cgroup.devices.allow = c 136:* rwm 488 lxc.cgroup.devices.allow = c 5:2 rwm 489 # rtc 490 lxc.cgroup.devices.allow = c 254:0 rwm 491 EOF 492 493 cat <<EOF > $config_path/fstab 494 proc $rootfs_path/proc proc nodev,noexec,nosuid 0 0 495 devpts $rootfs_path/dev/pts devpts defaults 0 0 496 sysfs $rootfs_path/sys sysfs defaults 0 0 497 EOF 498 if [ $? -ne 0 ]; then 499 echo "Failed to add configuration" 500 return 1 501 fi 502 503 return 0 504 } 505 506 clean() 507 { 508 509 if [ ! -e $cache ]; then 510 exit 0 511 fi 512 513 # lock, so we won't purge while someone is creating a repository 514 ( 515 flock -n -x 200 516 if [ $? != 0 ]; then 517 echo "Cache repository is busy." 518 exit 1 519 fi 520 521 echo -n "Purging the download cache for centos-$release..." 522 rm --preserve-root --one-file-system -rf $cache && echo "Done." || exit 1 523 exit 0 524 525 ) 200>/var/lock/subsys/lxc 526 } 527 528 usage() 529 { 530 cat <<EOF 531 usage: 532 $1 -n|--name=<container_name> 533 [-p|--path=<path>] [-c|--clean] [-R|--release=<centos_release>] [-A|--arch=<arch of the container>] 534 [-h|--help] 535 Mandatory args: 536 -n,--name container name, used to as an identifier for that container from now on 537 Optional args: 538 -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 539 -c,--clean clean the cache 540 -R,--release centos release for the new container. if the host is centos, then it will defaultto the host's release. 541 -A,--arch NOT USED YET. Define what arch the container will be [i686,x86_64] 542 -h,--help print this help 543 EOF 544 return 0 545 } 546 547 options=$(getopt -o hp:n:cR: -l help,path:,name:,clean,release: -- "$@") 548 if [ $? -ne 0 ]; then 549 usage $(basename $0) 550 exit 1 551 fi 552 eval set -- "$options" 553 554 while true 555 do 556 case "$1" in 557 -h|--help) usage $0 && exit 0;; 558 -p|--path) path=$2; shift 2;; 559 -n|--name) name=$2; shift 2;; 560 -c|--clean) clean=$2; shift 2;; 561 -R|--release) release=$2; shift 2;; 562 --) shift 1; break ;; 563 *) break ;; 564 esac 565 done 566 567 if [ ! -z "$clean" -a -z "$path" ]; then 568 clean || exit 1 569 exit 0 570 fi 571 572 yum_lite='0' 573 type yum >/dev/null 2>&1 574 if [ $? -ne 0 ]; then 575 echo "'yum' command is missing -- installing first" 576 yum_lite='1' 577 fi 578 579 if [ -z "$path" ]; then 580 path=$default_path 581 fi 582 583 if [ -z "$release" ]; then 584 if [ "$is_centos" ]; then 585 release=$(cat /etc/centos-release |awk '/^centos/ {print $3}') 586 else 587 echo "This is not a centos host and release missing, defaulting to 6. use -R|--release to specify release" 588 release=6 589 fi 590 fi 591 592 if [ "$(id -u)" != "0" ]; then 593 echo "This script should be run as 'root'" 594 exit 1 595 fi 596 597 598 rootfs_path=$path/rootfs 599 config_path=$path 600 cache=$cache_base/$release 601 602 revert() 603 { 604 echo "Interrupted, so cleaning up" 605 lxc-destroy -n $name 606 # maybe was interrupted before copy config 607 rm -rf $path/$name 608 rm -rf $default_path/$name 609 echo "exiting..." 610 exit 1 611 } 612 613 trap revert SIGHUP SIGINT SIGTERM 614 615 copy_configuration 616 if [ $? -ne 0 ]; then 617 echo "failed write configuration file" 618 exit 1 619 fi 620 621 install_centos 622 if [ $? -ne 0 ]; then 623 echo "failed to install centos" 624 exit 1 625 fi 626 627 configure_centos 628 if [ $? -ne 0 ]; then 629 echo "failed to configure centos for a container" 630 exit 1 631 fi 632 633 634 if [ ! -z $clean ]; then 635 clean || exit 1 636 exit 0 637 fi 638 echo "container rootfs and config created" |