3 # Client script for LXC container images.
5 # Copyright © 2014 Stéphane Graber <stgraber@ubuntu.com>
7 # This library is free software; you can redistribute it and/or
8 # modify it under the terms of the GNU Lesser General Public
9 # License as published by the Free Software Foundation; either
10 # version 2.1 of the License, or (at your option) any later version.
12 # This library is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 # Lesser General Public License for more details.
17 # You should have received a copy of the GNU Lesser General Public
18 # License along with this library; if not, write to the Free Software
19 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
24 LXC_TEMPLATE_CONFIG
="@LXCTEMPLATECONFIG@"
25 LXC_HOOK_DIR
="@LXCHOOKDIR@"
26 LOCALSTATEDIR
="@LOCALSTATEDIR@"
32 DOWNLOAD_VARIANT
="default"
33 DOWNLOAD_SERVER
="images.linuxcontainers.org"
34 DOWNLOAD_KEYID
="0xBAEFF88C22F6E216"
35 DOWNLOAD_KEYSERVER
="pool.sks-keyservers.net"
36 DOWNLOAD_VALIDATE
="true"
37 DOWNLOAD_FLUSH_CACHE
="false"
38 DOWNLOAD_MODE
="system"
39 DOWNLOAD_USE_CACHE
="false"
41 DOWNLOAD_SHOW_HTTP_WARNING
="true"
42 DOWNLOAD_SHOW_GPG_WARNING
="true"
43 DOWNLOAD_COMPAT_LEVEL
=1
44 DOWNLOAD_LIST_IMAGES
="false"
52 # Some useful functions
54 if [ -d "$DOWNLOAD_TEMP" ]; then
60 if ! wget
-q https
://${DOWNLOAD_SERVER}/$1 -O $2 >/dev
/null
2>&1; then
61 if ! wget
-q http
://${DOWNLOAD_SERVER}/$1 -O $2 >/dev
/null
2>&1; then
62 if [ "$3" = "noexit" ]; then
65 echo "ERROR: Failed to download http://${DOWNLOAD_SERVER}/$1" 1>&2
68 elif [ "$DOWNLOAD_SHOW_HTTP_WARNING" = "true" ]; then
69 DOWNLOAD_SHOW_HTTP_WARNING
="false"
70 echo "WARNING: Failed to download the file over HTTPs." 1>&2
71 echo -n " The file was instead download over HTTP. " 1>&2
72 echo "A server replay attack may be possible!" 1>&2
78 if ! download_file
$1 $2 noexit
; then
79 if [ "$DOWNLOAD_VALIDATE" = "true" ]; then
80 if [ "$3" = "normal" ]; then
81 echo "ERROR: Failed to download http://${DOWNLOAD_SERVER}/$1" 1>&2
93 if [ "$DOWNLOAD_VALIDATE" = "false" ]; then
97 echo "Setting up the GPG keyring"
99 mkdir
-p "$DOWNLOAD_TEMP/gpg"
100 chmod 700 "$DOWNLOAD_TEMP/gpg"
101 export GNUPGHOME
="$DOWNLOAD_TEMP/gpg"
102 if ! gpg
--keyserver $DOWNLOAD_KEYSERVER \
103 --recv-keys ${DOWNLOAD_KEYID} >/dev
/null
2>&1; then
104 echo "ERROR: Unable to fetch GPG key from keyserver."
110 if [ "$DOWNLOAD_VALIDATE" = "false" ]; then
111 if [ "$DOWNLOAD_SHOW_GPG_WARNING" = "true" ]; then
112 echo "WARNING: Running without gpg validation!" 1>&2
114 DOWNLOAD_SHOW_GPG_WARNING
="false"
118 if ! gpg
--verify $1 >/dev
/zero
2>&1; then
119 echo "ERROR: Invalid signature for $1" 1>&2
125 [ -e /proc
/self
/uid_map
] ||
{ echo no
; return; }
126 [ "$(wc -l /proc/self/uid_map | awk '{ print $1 }')" -eq 1 ] || \
127 { echo yes; return; }
128 line
=$
(awk '{ print $1 " " $2 " " $3 }' /proc
/self
/uid_map
)
129 [ "$line" = "0 0 4294967295" ] && { echo no
; return; }
134 FILE_PATH
="${LXC_CACHE_PATH}/$1"
135 if [ -e "${FILE_PATH}-${DOWNLOAD_MODE}" ]; then
136 FILE_PATH
="${FILE_PATH}-${DOWNLOAD_MODE}"
138 if [ -e "$FILE_PATH.${DOWNLOAD_COMPAT_LEVEL}" ]; then
139 FILE_PATH
="${FILE_PATH}.${DOWNLOAD_COMPAT_LEVEL}"
147 LXC container image downloader
150 [ -d | --dist <distribution> ]: The name of the distribution
151 [ -r | --release <release> ]: Release name/version
152 [ -a | --arch <architecture> ]: Architecture of the container
155 [ -h | --help ]: This help message
156 [ -l | --list ]: List all available images
157 [ --variant <variant> ]: Variant of the image (default: "default")
158 [ --server <server> ]: Image server (default: "images.linuxcontainers.org")
159 [ --keyid <keyid> ]: GPG keyid (default: 0x...)
160 [ --keyserver <keyserver> ]: GPG keyserver to use
161 [ --no-validate ]: Disable GPG validation (not recommended)
162 [ --flush-cache ]: Flush the local copy (if present)
163 [ --force-cache ]; Force the use of the local copy even if expired
165 LXC internal arguments (do not pass manually!):
166 [ --name <name> ]: The container name
167 [ --path <path> ]: The path to the container
168 [ --rootfs <rootfs> ]: The path to the container's rootfs
169 [ --mapped-uid <map> ]: A uid/gid map (user namespaces)
174 options
=$
(getopt
-o d
:r
:a
:hl
-l dist
:,release
:,arch
:,help,list
,variant
:,\
175 server
:,keyid
:,no-validate
,flush-cache
,force-cache
:,name
:,path
:,\
176 rootfs
:,mapped-uid
: -- "$@")
178 if [ $?
-ne 0 ]; then
182 eval set -- "$options"
186 -h|
--help) usage
&& exit 1;;
187 -l|
--list) DOWNLOAD_LIST_IMAGES
="true"; shift 1;;
188 -d|
--dist) DOWNLOAD_DIST
=$2; shift 2;;
189 -r|
--release) DOWNLOAD_RELEASE
=$2; shift 2;;
190 -a|
--arch) DOWNLOAD_ARCH
=$2; shift 2;;
191 --variant) DOWNLOAD_VARIANT
=$2; shift 2;;
192 --server) DOWNLOAD_SERVER
=$2; shift 2;;
193 --keyid) DOWNLOAD_KEYID
=$2; shift 2;;
194 --no-validate) DOWNLOAD_VALIDATE
="false"; shift 1;;
195 --flush-cache) DOWNLOAD_FLUSH_CACHE
="true"; shift 1;;
196 --force-cache) DOWNLOAD_FORCE_CACHE
="true"; shift 1;;
197 --name) LXC_NAME
=$2; shift 2;;
198 --path) LXC_PATH
=$2; shift 2;;
199 --rootfs) LXC_ROOTFS
=$2; shift 2;;
200 --mapped-uid) LXC_MAPPED_UID
=$2; shift 2;;
205 # Check for required binaries
206 for bin
in tar xz wget
; do
207 if ! type $bin >/dev
/null
2>&1; then
208 echo "ERROR: Missing required tool: $bin" 1>&2
214 if [ "$DOWNLOAD_VALIDATE" = "true" ]; then
215 if ! type gpg
>/dev
/null
2>&1; then
216 echo "ERROR: Missing recommended tool: gpg" 1>&2
217 echo "You can workaround this by using --no-validate." 1>&2
222 # Check that we have all variables we need
223 if [ -z "$LXC_NAME" ] ||
[ -z "$LXC_PATH" ] ||
[ -z "$LXC_ROOTFS" ]; then
224 echo "ERROR: Not running through LXC." 1>&2
228 if [ "$(in_userns)" = "yes" ]; then
229 if [ -z "$LXC_MAPPED_UID" ] ||
[ "$LXC_MAPPED_UID" = "-1" ]; then
230 echo "ERROR: In a user namespace without a map." 1>&2
236 if ([ -z "$DOWNLOAD_DIST" ] ||
[ -z "$DOWNLOAD_RELEASE" ] || \
237 [ -z "$DOWNLOAD_ARCH" ]) && [ "$DOWNLOAD_LIST_IMAGES" = "false" ]; then
238 echo "ERROR: Missing required argument" 1>&2
243 # Trap all exit signals
244 trap cleanup EXIT HUP INT TERM
245 DOWNLOAD_TEMP
=$
(mktemp
-d)
248 if [ "$DOWNLOAD_LIST_IMAGES" = "true" ]; then
253 DOWNLOAD_INDEX_PATH
=/meta
/1.0/index-
${DOWNLOAD_MODE}
255 echo "Downloading the image index"
256 if ! download_file
${DOWNLOAD_INDEX_PATH}.
${DOWNLOAD_COMPAT_LEVEL} \
257 ${DOWNLOAD_TEMP}/index noexit ||
258 ! download_sig
${DOWNLOAD_INDEX_PATH}.
${DOWNLOAD_COMPAT_LEVEL}.asc \
259 ${DOWNLOAD_TEMP}/index.asc noexit
; then
260 download_file
${DOWNLOAD_INDEX_PATH} ${DOWNLOAD_TEMP}/index normal
261 download_sig
${DOWNLOAD_INDEX_PATH}.asc \
262 ${DOWNLOAD_TEMP}/index.asc normal
265 gpg_validate
${DOWNLOAD_TEMP}/index.asc
270 echo "DIST\tRELEASE\tARCH\tVARIANT\tBUILD"
279 [ -n "$DOWNLOAD_DIST" ] && [ "$1" != "$DOWNLOAD_DIST" ] && continue
280 [ -n "$DOWNLOAD_RELEASE" ] && [ "$2" != "$DOWNLOAD_RELEASE" ] && continue
281 [ -n "$DOWNLOAD_ARCH" ] && [ "$3" != "$DOWNLOAD_ARCH" ] && continue
282 [ -n "$DOWNLOAD_VARIANT" ] && [ "$4" != "$DOWNLOAD_VARIANT" ] && continue
283 [ -z "$5" ] ||
[ -z "$6" ] && continue
285 echo "$1\t$2\t$3\t$4\t$5"
286 done < ${DOWNLOAD_TEMP}/index
293 if [ "$DOWNLOAD_MODE" = "system" ]; then
294 LXC_CACHE_BASE
="$LOCALSTATEDIR/cache/"
295 LXC_CACHE_PATH
="$LOCALSTATEDIR/cache/lxc/download/$DOWNLOAD_DIST"
296 LXC_CACHE_PATH
="$LXC_CACHE_PATH/$DOWNLOAD_RELEASE/$DOWNLOAD_ARCH"
298 LXC_CACHE_BASE
="$HOME/.cache/lxc/"
299 LXC_CACHE_PATH
="$HOME/.cache/lxc/download/$DOWNLOAD_DIST"
300 LXC_CACHE_PATH
="$LXC_CACHE_PATH/$DOWNLOAD_RELEASE/$DOWNLOAD_ARCH"
303 if [ -d "$LXC_CACHE_PATH" ]; then
304 if [ "$DOWNLOAD_FLUSH_CACHE" = "true" ]; then
305 echo "Flushing the cache..."
306 rm -Rf $LXC_CACHE_PATH
307 elif [ "$DOWNLOAD_FORCE_CACHE" = "true" ]; then
308 DOWNLOAD_USE_CACHE
="true"
310 DOWNLOAD_USE_CACHE
="true"
311 if [ -e "$(relevant_file expiry)" ]; then
312 if [ "$(cat $(relevant_file expiry))" -lt $
(date +%s
) ]; then
313 echo "The cached copy has expired, re-downloading..."
314 DOWNLOAD_USE_CACHE
="false"
320 # Download what's needed
321 if [ "$DOWNLOAD_USE_CACHE" = "false" ]; then
326 DOWNLOAD_INDEX_PATH
=/meta
/1.0/index-
${DOWNLOAD_MODE}
328 echo "Downloading the image index"
329 if ! download_file
${DOWNLOAD_INDEX_PATH}.
${DOWNLOAD_COMPAT_LEVEL} \
330 ${DOWNLOAD_TEMP}/index noexit ||
331 ! download_sig
${DOWNLOAD_INDEX_PATH}.
${DOWNLOAD_COMPAT_LEVEL}.asc \
332 ${DOWNLOAD_TEMP}/index.asc noexit
; then
333 download_file
${DOWNLOAD_INDEX_PATH} ${DOWNLOAD_TEMP}/index normal
334 download_sig
${DOWNLOAD_INDEX_PATH}.asc \
335 ${DOWNLOAD_TEMP}/index.asc normal
338 gpg_validate
${DOWNLOAD_TEMP}/index.asc
348 if [ "$1" != "$DOWNLOAD_DIST" ] || \
349 [ "$2" != "$DOWNLOAD_RELEASE" ] || \
350 [ "$3" != "$DOWNLOAD_ARCH" ] || \
351 [ "$4" != "$DOWNLOAD_VARIANT" ] || \
359 done < ${DOWNLOAD_TEMP}/index
361 if [ -z "$DOWNLOAD_URL" ]; then
362 echo "ERROR: Couldn't find a matching image." 1>&1
366 if [ -d "$LXC_CACHE_PATH" ] && [ -f "$LXC_CACHE_PATH/build_id" ] && \
367 [ "$(cat $LXC_CACHE_PATH/build_id)" = "$DOWNLOAD_BUILD" ]; then
368 echo "The cache is already up to date."
369 echo "Using image from local cache"
371 # Download the actual files
372 echo "Downloading the rootfs"
373 download_file
$DOWNLOAD_URL/rootfs.
tar.xz \
374 ${DOWNLOAD_TEMP}/rootfs.
tar.xz normal
375 download_sig
$DOWNLOAD_URL/rootfs.
tar.xz.asc \
376 ${DOWNLOAD_TEMP}/rootfs.
tar.xz.asc normal
377 gpg_validate
${DOWNLOAD_TEMP}/rootfs.
tar.xz.asc
379 echo "Downloading the metadata"
380 download_file
$DOWNLOAD_URL/meta.
tar.xz \
381 ${DOWNLOAD_TEMP}/meta.
tar.xz normal
382 download_sig
$DOWNLOAD_URL/meta.
tar.xz.asc \
383 ${DOWNLOAD_TEMP}/meta.
tar.xz.asc normal
384 gpg_validate
${DOWNLOAD_TEMP}/meta.
tar.xz.asc
386 if [ -d $LXC_CACHE_PATH ]; then
387 rm -Rf $LXC_CACHE_PATH
389 mkdir
-p $LXC_CACHE_PATH
390 mv ${DOWNLOAD_TEMP}/rootfs.
tar.xz
$LXC_CACHE_PATH
391 if ! tar Jxf
${DOWNLOAD_TEMP}/meta.
tar.xz
-C $LXC_CACHE_PATH; then
392 echo "ERROR: Invalid rootfs tarball." 2>&1
396 echo $DOWNLOAD_BUILD > $LXC_CACHE_PATH/build_id
398 if [ -n "$LXC_MAPPED_UID" ] && [ "$LXC_MAPPED_UID" != "-1" ]; then
399 chown
$LXC_MAPPED_UID -Rf $LXC_CACHE_BASE >/dev
/null
2>&1 || true
401 echo "The image cache is now ready"
404 echo "Using image from local cache"
408 echo "Unpacking the rootfs"
411 excludelist
=$
(relevant_file excludes
)
412 if [ -f "${excludelist}" ]; then
414 EXCLUDES
="$EXCLUDES --exclude=$line"
418 tar --anchored ${EXCLUDES} --numeric-owner -xpJf \
419 ${LXC_CACHE_PATH}/rootfs.
tar.xz
-C ${LXC_ROOTFS}
421 mkdir
-p ${LXC_ROOTFS}/dev
/pts
/
423 # Setup the configuration
424 configfile
=$
(relevant_file config
)
425 fstab
=$
(relevant_file fstab
)
426 if [ ! -e $configfile ]; then
427 echo "ERROR: meta tarball is missing the configuration file" 1>&2
431 ## Extract all the network config entries
432 sed -i -e "/lxc.network/{w ${LXC_PATH}/config-network" -e "d}" \
435 ## Extract any other config entry
436 sed -i -e "/lxc./{w ${LXC_PATH}/config-auto" -e "d}" ${LXC_PATH}/config
438 ## Append the defaults
439 echo "" >> ${LXC_PATH}/config
440 echo "# Distribution configuration" >> ${LXC_PATH}/config
441 cat $configfile >> ${LXC_PATH}/config
443 ## Add the container-specific config
444 echo "" >> ${LXC_PATH}/config
445 echo "# Container specific configuration" >> ${LXC_PATH}/config
446 if [ -e "${LXC_PATH}/config-auto" ]; then
447 cat ${LXC_PATH}/config-auto
>> ${LXC_PATH}/config
448 rm ${LXC_PATH}/config-auto
450 if [ -e "$fstab" ]; then
451 echo "lxc.mount = ${LXC_PATH}/fstab" >> ${LXC_PATH}/config
453 echo "lxc.utsname = ${LXC_NAME}" >> ${LXC_PATH}/config
455 ## Re-add the previously removed network config
456 if [ -e "${LXC_PATH}/config-network" ]; then
457 echo "" >> ${LXC_PATH}/config
458 echo "# Network configuration" >> ${LXC_PATH}/config
459 cat ${LXC_PATH}/config-network
>> ${LXC_PATH}/config
460 rm ${LXC_PATH}/config-network
463 TEMPLATE_FILES
="${LXC_PATH}/config"
466 if [ -e $fstab ]; then
467 cp ${fstab} ${LXC_PATH}/fstab
468 TEMPLATE_FILES
="$TEMPLATE_FILES ${LXC_PATH}/fstab"
471 # Look for extra templates
472 if [ -e "$(relevant_file templates)" ]; then
474 fullpath
=${LXC_ROOTFS}/$line
475 [ ! -e "$fullpath" ] && continue
476 TEMPLATE_FILES
="$TEMPLATE_FILES $fullpath"
477 done < $
(relevant_file templates
)
480 # Replace variables in all templates
481 for file in $TEMPLATE_FILES; do
482 [ ! -f "$file" ] && continue
484 sed -i "s#LXC_NAME#$LXC_NAME#g" $file
485 sed -i "s#LXC_PATH#$LXC_PATH#g" $file
486 sed -i "s#LXC_ROOTFS#$LXC_ROOTFS#g" $file
487 sed -i "s#LXC_TEMPLATE_CONFIG#$LXC_TEMPLATE_CONFIG#g" $file
488 sed -i "s#LXC_HOOK_DIR#$LXC_HOOK_DIR#g" $file
491 if [ -n "$LXC_MAPPED_UID" ] && [ "$LXC_MAPPED_UID" != "-1" ]; then
492 chown
$LXC_MAPPED_UID -f $LXC_PATH/config
$LXC_PATH/fstab || true
495 if [ -e "$(relevant_file create-message)" ]; then
498 cat "$(relevant_file create-message)"