#!/bin/bash # template script for generating ubuntu container for LXC # # This script consolidates and extends the existing lxc ubuntu scripts # # Copyright © 2013 Canonical Ltd. # Author: Scott Moser # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2, as # published by the Free Software Foundation. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # Detect use under userns (unsupported) # Make sure the usual locations are in PATH export PATH=$PATH:/usr/sbin:/usr/bin:/sbin:/bin VERBOSITY=0 DOWNLOAD_URL="http://download.cirros-cloud.net/" UNAME_M=$(uname -m) ARCHES=( i386 x86_64 amd64 arm ) STREAMS=( released devel ) SOURCES=( nocloud none ) BUILD="standard" LXC_TEMPLATE_CONFIG="@LXCTEMPLATECONFIG@" LXC_MAPPED_GID= LXC_MAPPED_UID= DEF_VERSION="released" DEF_SOURCE="nocloud" case "${UNAME_M}" in i?86) DEF_ARCH="i386";; x86_64) DEF_ARCH="x86_64";; arm*) DEF_ARCH="arm";; *) DEF_ARCH="i386";; esac am_in_userns() { [ -e /proc/self/uid_map ] || { echo no; return; } [ "$(wc -l /proc/self/uid_map | awk '{ print $1 }')" -eq 1 ] || { echo yes; return; } line=$(awk '{ print $1 " " $2 " " $3 }' /proc/self/uid_map) [ "$line" = "0 0 4294967295" ] && { echo no; return; } echo yes } in_userns=0 [ $(am_in_userns) = "yes" ] && in_userns=1 # Allow the cache base to be set by environment variable if [ $(id -u) -eq 0 ]; then CACHE_D=${LXC_CACHE_PATH:-"@LOCALSTATEDIR@/cache/lxc/cirros"} else CACHE_D=${LXC_CACHE_PATH:-"$HOME/.cache/lxc/cirros"} fi error() { echo "$@" 1>&2; } inargs() { local needle="$1" x="" shift for x in "$@"; do [ "$needle" = "$x" ] && return 0 done return 1 } Usage() { cat <&2; [ $# -eq 0 ] || error "$@"; return 1; } debug() { local level=${1}; shift; [ "${level}" -gt "${VERBOSITY}" ] && return error "${@}" } jsondict() { local k="" v="" ret="{" for arg in "$@"; do k="${arg%%=*}" v="${arg#*=}" ret="${ret} \"${k}\": \"$v\"," done ret="${ret%,} }" echo "$ret" } copy_configuration() { local path=$1 rootfs=$2 name=$3 arch=$4 release=$5 cat >> "$path/config" <> $path/config fi } insert_ds_nocloud() { local root_d="$1" authkey="$2" udfile="$3" local sdir="$root_d/var/lib/cloud/seed/nocloud" mkdir -p "$sdir" || { error "failed to make datasource dir $sdir"; return 1; } rm -f "$sdir/meta-data" "$sdir/user-data" || { error "failed to clean old data from $sdir"; return 1; } iid="iid-local01" jsondict "instance-id=$iid" \ ${authkeys:+"public-keys=${authkeys}"} > "$sdir/meta-data" || { error "failed to write metadata to $sdir/meta-data"; return 1; } if [ -n "$udfile" ]; then cat "$udfile" > "$sdir/user-data" || { error "failed to write user-data to $sdir"; return 1; } else rm -f "$sdir/user-data" fi } insert_ds() { local dstype="$1" root_d="$2" authkey="$3" udfile="$4" case "$dstype" in nocloud) insert_ds_nocloud "$root_d" "$authkey" "$udfile" esac } extract_rootfs() { local tarball="$1" rootfs_d="$2" mkdir -p "${rootfs_d}" || { error "failed to make rootfs dir ${rootfs_d}"; return 1; } if [ $in_userns -eq 1 ]; then tar -C "${rootfs_d}" --anchored --exclude="dev/*" -Sxzf "${tarball}" || { error "failed to populate ${rootfs_d}"; return 1; } else tar -C "${rootfs_d}" -Sxzf "${tarball}" || { error "failed to populate ${rootfs_d}"; return 1; } fi return 0 } download_tarball() { local arch="$1" ver="$2" cached="$3" baseurl="$4" local out="" outd="" file="" dlpath="" file="cirros-$ver-$arch-lxc.tar.gz" dlpath="$ver/$file" outd="${cached}/${dlpath%/*}" if [ -f "$cached/$dlpath" ]; then _RET="$cached/$dlpath" return 0 fi mkdir -p "${outd}" || { error "failed to create ${outd}"; return 1; } debug 1 "downloading ${baseurl%/}/$dlpath" to "${cached}/$dlpath" wget "${baseurl%/}/$dlpath" -O "$cached/${dlpath}.$$" && mv "$cached/$dlpath.$$" "$cached/$dlpath" || { rm -f "$cached/$dlpath.$$"; error "failed to download $dlpath"; return 1; } _RET="$cached/$dlpath" } create_main() { local short_opts="a:hn:p:S:uvV" local long_opts="arch:,auth-key:,name:,path:,tarball:,userdata:,verbose,version:,rootfs:,mapped-uid:,mapped-gid:" local getopt_out="" getopt_out=$(getopt --name "${0##*/}" \ --options "${short_opts}" --long "${long_opts}" -- "$@") && eval set -- "${getopt_out}" || { bad_Usage; return; } local arch="${DEF_ARCH}" dsource="${DEF_SOURCE}" version="${DEF_VERSION}" local authkey_f="" authkeys="" userdata_f="" path="" tarball="" local cur="" next="" local rootfs_d="" while [ $# -ne 0 ]; do cur=$1; next=$2; case "$cur" in -a|--arch) arch="$next"; shift;; -h|--help) Usage ; return 0;; -n|--name) name="$next"; shift;; -v|--verbose) VERBOSITY=$((${VERBOSITY}+1));; -S|--auth-key) authkey_f="$next"; shift;; -p|--path) path=$next; shift;; -v|--version) version=$next; shift;; -u|--userdata) userdata_f="$next"; shift;; --tarball) tarball="$next"; shift;; --source) dsource="$next"; shift;; --rootfs) rootfs_d="$next"; shift;; --mapped-uid) LXC_MAPPED_UID=$next; shift;; --mapped-gid) LXC_MAPPED_GID=$next; shift;; --) shift; break;; esac shift; done [ -n "$rootfs_d" ] || rootfs_d="$path/rootfs" [ $# -eq 0 ] || { bad_Usage "unexpected arguments: $*"; return; } [ -n "$path" ] || { error "'path' parameter is required"; return 1; } if [ "$(id -u)" != "0" ]; then { error "must be run as root"; return 1; } fi case "$arch" in i?86) arch="i386";; amd64) arch="x86_64";; esac inargs "$arch" "${ARCHES[@]}" || { error "bad arch '$arch'. allowed: ${ARCHES[*]}"; return 1; } inargs "$dsource" "${SOURCES[@]}" || { error "bad source '$dsource'. allowed: ${SOURCES[*]}"; return 1; } if [ "$dsource" = "none" ] && [ -n "$userdata_f" -o -n "$authkey_f" ]; then error "userdata and authkey are incompatible with --source=none"; return 1; fi if [ -n "$authkey_f" ]; then if [ ! -f "$authkey_f" ]; then error "--auth-key=${authkey_f} must reference a file" return 1 fi authkeys=$(cat "$authkey_f") || { error "failed to read ${authkey_f}"; return 1; } fi if [ -n "$userdata_f" -a ! -f "${userdata_f}" ]; then error "${userdata_f}: --userdata arg not a file" return 1 fi if [ -z "$tarball" ]; then if inargs "$version" "${STREAMS[@]}"; then out=$(wget -O - -q "${DOWNLOAD_URL%/}/version/$version") || { error "failed to convert 'version=$version'"; return 1; } version="$out" fi download_tarball "$arch" "$version" "${CACHE_D}" "${DOWNLOAD_URL}" || return tarball="$_RET" fi extract_rootfs "${tarball}" "${rootfs_d}" || return if [ "$version" = "0.3.2~pre1" ]; then debug 1 "fixing console for lxc and '$version'" sed -i 's,^\(#console.* 115200 \)# /dev/console,\1 console,g' \ "$rootfs_d/etc/inittab" || { error "failed to fix console entry for $version"; return 1; } fi if [ "$dsource" != "none" ]; then insert_ds "$dsource" "$path/rootfs" "$authkeys" "$userdata_f" || { error "failed to insert userdata to $path/rootfs" return 1 } fi copy_configuration "$path" "$path/rootfs" "$name" "$arch" "$release" return } create_main "$@" # vi: ts=4 expandtab