]> git.proxmox.com Git - mirror_lxc.git/blob - templates/lxc-download.in
download: Improve cache handling
[mirror_lxc.git] / templates / lxc-download.in
1 #!/bin/sh
2
3 # Client script for LXC container images.
4 #
5 # Copyright © 2014 Stéphane Graber <stgraber@ubuntu.com>
6 #
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.
11
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.
16
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
20 # USA
21
22 set -eu
23
24 LXC_TEMPLATE_CONFIG="@LXCTEMPLATECONFIG@"
25 LXC_HOOK_DIR="@LXCHOOKDIR@"
26 LOCALSTATEDIR="@LOCALSTATEDIR@"
27
28 # Defaults
29 DOWNLOAD_DIST=
30 DOWNLOAD_RELEASE=
31 DOWNLOAD_ARCH=
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"
40 DOWNLOAD_URL=
41 DOWNLOAD_SHOW_HTTP_WARNING="true"
42 DOWNLOAD_SHOW_GPG_WARNING="true"
43 DOWNLOAD_COMPAT_LEVEL=1
44 DOWNLOAD_LIST_IMAGES="false"
45 DOWNLOAD_BUILD=
46
47 LXC_NAME=
48 LXC_PATH=
49 LXC_ROOTFS=
50 LXC_MAPPED_UID=
51
52 # Some useful functions
53 cleanup() {
54 if [ -d "$DOWNLOAD_TEMP" ]; then
55 rm -Rf $DOWNLOAD_TEMP
56 fi
57 }
58
59 download_file() {
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
63 return 1
64 else
65 echo "ERROR: Failed to download http://${DOWNLOAD_SERVER}/$1" 1>&2
66 exit 1
67 fi
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
73 fi
74 fi
75 }
76
77 download_sig() {
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
82 exit 1
83 else
84 return 1
85 fi
86 else
87 return 0
88 fi
89 fi
90 }
91
92 gpg_setup() {
93 if [ "$DOWNLOAD_VALIDATE" = "false" ]; then
94 return
95 fi
96
97 echo "Setting up the GPG keyring"
98
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."
105 exit 1
106 fi
107 }
108
109 gpg_validate() {
110 if [ "$DOWNLOAD_VALIDATE" = "false" ]; then
111 if [ "$DOWNLOAD_SHOW_GPG_WARNING" = "true" ]; then
112 echo "WARNING: Running without gpg validation!" 1>&2
113 fi
114 DOWNLOAD_SHOW_GPG_WARNING="false"
115 return 0
116 fi
117
118 if ! gpg --verify $1 >/dev/zero 2>&1; then
119 echo "ERROR: Invalid signature for $1" 1>&2
120 exit 1
121 fi
122 }
123
124 in_userns() {
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; }
130 echo yes
131 }
132
133 relevant_file() {
134 FILE_PATH="${LXC_CACHE_PATH}/$1"
135 if [ -e "${FILE_PATH}-${DOWNLOAD_MODE}" ]; then
136 FILE_PATH="${FILE_PATH}-${DOWNLOAD_MODE}"
137 fi
138 if [ -e "$FILE_PATH.${DOWNLOAD_COMPAT_LEVEL}" ]; then
139 FILE_PATH="${FILE_PATH}.${DOWNLOAD_COMPAT_LEVEL}"
140 fi
141
142 echo $FILE_PATH
143 }
144
145 usage() {
146 cat <<EOF
147 LXC container image downloader
148
149 Required arguments:
150 [ -d | --dist <distribution> ]: The name of the distribution
151 [ -r | --release <release> ]: Release name/version
152 [ -a | --arch <architecture> ]: Architecture of the container
153
154 Optional arguments:
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
164
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)
170 EOF
171 return 0
172 }
173
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: -- "$@")
177
178 if [ $? -ne 0 ]; then
179 usage
180 exit 1
181 fi
182 eval set -- "$options"
183
184 while :; do
185 case "$1" in
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;;
201 *) break;;
202 esac
203 done
204
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
209 exit 1
210 fi
211 done
212
213 # Check for GPG
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
218 exit 1
219 fi
220 fi
221
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
225 exit 1
226 fi
227
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
231 exit 1
232 fi
233 DOWNLOAD_MODE="user"
234 fi
235
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
239 usage
240 exit 1
241 fi
242
243 # Trap all exit signals
244 trap cleanup EXIT HUP INT TERM
245 DOWNLOAD_TEMP=$(mktemp -d)
246
247 # Simply list images
248 if [ "$DOWNLOAD_LIST_IMAGES" = "true" ]; then
249 # Initialize GPG
250 gpg_setup
251
252 # Grab the index
253 DOWNLOAD_INDEX_PATH=/meta/1.0/index-${DOWNLOAD_MODE}
254
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
263 fi
264
265 gpg_validate ${DOWNLOAD_TEMP}/index.asc
266
267 # Parse it
268 echo ""
269 echo "---"
270 echo "DIST\tRELEASE\tARCH\tVARIANT\tBUILD"
271 echo "---"
272 while read line; do
273 # Basic CSV parser
274 OLD_IFS=$IFS
275 IFS=";"
276 set -- $line
277 IFS=$OLD_IFS
278
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
284
285 echo "$1\t$2\t$3\t$4\t$5"
286 done < ${DOWNLOAD_TEMP}/index
287 echo "---"
288
289 exit 1
290 fi
291
292 # Setup the cache
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"
297 else
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"
301 fi
302
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"
309 else
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"
315 fi
316 fi
317 fi
318 fi
319
320 # Download what's needed
321 if [ "$DOWNLOAD_USE_CACHE" = "false" ]; then
322 # Initialize GPG
323 gpg_setup
324
325 # Grab the index
326 DOWNLOAD_INDEX_PATH=/meta/1.0/index-${DOWNLOAD_MODE}
327
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
336 fi
337
338 gpg_validate ${DOWNLOAD_TEMP}/index.asc
339
340 # Parse it
341 while read line; do
342 # Basic CSV parser
343 OLD_IFS=$IFS
344 IFS=";"
345 set -- $line
346 IFS=$OLD_IFS
347
348 if [ "$1" != "$DOWNLOAD_DIST" ] || \
349 [ "$2" != "$DOWNLOAD_RELEASE" ] || \
350 [ "$3" != "$DOWNLOAD_ARCH" ] || \
351 [ "$4" != "$DOWNLOAD_VARIANT" ] || \
352 [ -z "$6" ]; then
353 continue
354 fi
355
356 DOWNLOAD_BUILD=$5
357 DOWNLOAD_URL=$6
358 break
359 done < ${DOWNLOAD_TEMP}/index
360
361 if [ -z "$DOWNLOAD_URL" ]; then
362 echo "ERROR: Couldn't find a matching image." 1>&1
363 exit 1
364 fi
365
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"
370 else
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
378
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
385
386 if [ -d $LXC_CACHE_PATH ]; then
387 rm -Rf $LXC_CACHE_PATH
388 fi
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
393 exit 1
394 fi
395
396 echo $DOWNLOAD_BUILD > $LXC_CACHE_PATH/build_id
397
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
400 fi
401 echo "The image cache is now ready"
402 fi
403 else
404 echo "Using image from local cache"
405 fi
406
407 # Unpack the rootfs
408 echo "Unpacking the rootfs"
409
410 EXCLUDES=""
411 excludelist=$(relevant_file excludes)
412 if [ -f "${excludelist}" ]; then
413 while read line; do
414 EXCLUDES="$EXCLUDES --exclude=$line"
415 done < $excludelist
416 fi
417
418 tar --anchored ${EXCLUDES} --numeric-owner -xpJf \
419 ${LXC_CACHE_PATH}/rootfs.tar.xz -C ${LXC_ROOTFS}
420
421 mkdir -p ${LXC_ROOTFS}/dev/pts/
422
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
428 exit 1
429 fi
430
431 ## Extract all the network config entries
432 sed -i -e "/lxc.network/{w ${LXC_PATH}/config-network" -e "d}" \
433 ${LXC_PATH}/config
434
435 ## Extract any other config entry
436 sed -i -e "/lxc./{w ${LXC_PATH}/config-auto" -e "d}" ${LXC_PATH}/config
437
438 ## Append the defaults
439 echo "" >> ${LXC_PATH}/config
440 echo "# Distribution configuration" >> ${LXC_PATH}/config
441 cat $configfile >> ${LXC_PATH}/config
442
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
449 fi
450 if [ -e "$fstab" ]; then
451 echo "lxc.mount = ${LXC_PATH}/fstab" >> ${LXC_PATH}/config
452 fi
453 echo "lxc.utsname = ${LXC_NAME}" >> ${LXC_PATH}/config
454
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
461 fi
462
463 TEMPLATE_FILES="${LXC_PATH}/config"
464
465 # Setup the fstab
466 if [ -e $fstab ]; then
467 cp ${fstab} ${LXC_PATH}/fstab
468 TEMPLATE_FILES="$TEMPLATE_FILES ${LXC_PATH}/fstab"
469 fi
470
471 # Look for extra templates
472 if [ -e "$(relevant_file templates)" ]; then
473 while read line; do
474 fullpath=${LXC_ROOTFS}/$line
475 [ ! -e "$fullpath" ] && continue
476 TEMPLATE_FILES="$TEMPLATE_FILES $fullpath"
477 done < $(relevant_file templates)
478 fi
479
480 # Replace variables in all templates
481 for file in $TEMPLATE_FILES; do
482 [ ! -f "$file" ] && continue
483
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
489 done
490
491 if [ -n "$LXC_MAPPED_UID" ] && [ "$LXC_MAPPED_UID" != "-1" ]; then
492 chown $LXC_MAPPED_UID -f $LXC_PATH/config $LXC_PATH/fstab || true
493 fi
494
495 if [ -e "$(relevant_file create-message)" ]; then
496 echo ""
497 echo "---"
498 cat "$(relevant_file create-message)"
499 fi
500
501 exit 0