]> git.proxmox.com Git - mirror_lxc.git/blob - templates/lxc-download.in
lxc-download: Add interactive mode
[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_FORCE_CACHE="false"
39 DOWNLOAD_MODE="system"
40 DOWNLOAD_USE_CACHE="false"
41 DOWNLOAD_URL=
42 DOWNLOAD_SHOW_HTTP_WARNING="true"
43 DOWNLOAD_SHOW_GPG_WARNING="true"
44 DOWNLOAD_READY_GPG="false"
45 DOWNLOAD_COMPAT_LEVEL=1
46 DOWNLOAD_LIST_IMAGES="false"
47 DOWNLOAD_BUILD=
48 DOWNLOAD_INTERACTIVE="false"
49
50 LXC_NAME=
51 LXC_PATH=
52 LXC_ROOTFS=
53 LXC_MAPPED_UID=
54
55 # Some useful functions
56 cleanup() {
57 if [ -d "$DOWNLOAD_TEMP" ]; then
58 rm -Rf $DOWNLOAD_TEMP
59 fi
60 }
61
62 download_file() {
63 if ! wget -q https://${DOWNLOAD_SERVER}/$1 -O $2 >/dev/null 2>&1; then
64 if ! wget -q http://${DOWNLOAD_SERVER}/$1 -O $2 >/dev/null 2>&1; then
65 if [ "$3" = "noexit" ]; then
66 return 1
67 else
68 echo "ERROR: Failed to download http://${DOWNLOAD_SERVER}/$1" 1>&2
69 exit 1
70 fi
71 elif [ "$DOWNLOAD_SHOW_HTTP_WARNING" = "true" ]; then
72 DOWNLOAD_SHOW_HTTP_WARNING="false"
73 echo "WARNING: Failed to download the file over HTTPs." 1>&2
74 echo -n " The file was instead download over HTTP. " 1>&2
75 echo "A server replay attack may be possible!" 1>&2
76 fi
77 fi
78 }
79
80 download_sig() {
81 if ! download_file $1 $2 noexit; then
82 if [ "$DOWNLOAD_VALIDATE" = "true" ]; then
83 if [ "$3" = "normal" ]; then
84 echo "ERROR: Failed to download http://${DOWNLOAD_SERVER}/$1" 1>&2
85 exit 1
86 else
87 return 1
88 fi
89 else
90 return 0
91 fi
92 fi
93 }
94
95 gpg_setup() {
96 if [ "$DOWNLOAD_VALIDATE" = "false" ]; then
97 return
98 fi
99
100 if [ "$DOWNLOAD_READY_GPG" = "true" ]; then
101 return
102 fi
103
104 echo "Setting up the GPG keyring"
105
106 mkdir -p "$DOWNLOAD_TEMP/gpg"
107 chmod 700 "$DOWNLOAD_TEMP/gpg"
108 export GNUPGHOME="$DOWNLOAD_TEMP/gpg"
109 if ! gpg --keyserver $DOWNLOAD_KEYSERVER \
110 --recv-keys ${DOWNLOAD_KEYID} >/dev/null 2>&1; then
111 echo "ERROR: Unable to fetch GPG key from keyserver."
112 exit 1
113 fi
114
115 DOWNLOAD_READY_GPG="true"
116 }
117
118 gpg_validate() {
119 if [ "$DOWNLOAD_VALIDATE" = "false" ]; then
120 if [ "$DOWNLOAD_SHOW_GPG_WARNING" = "true" ]; then
121 echo "WARNING: Running without gpg validation!" 1>&2
122 fi
123 DOWNLOAD_SHOW_GPG_WARNING="false"
124 return 0
125 fi
126
127 if ! gpg --verify $1 >/dev/zero 2>&1; then
128 echo "ERROR: Invalid signature for $1" 1>&2
129 exit 1
130 fi
131 }
132
133 in_userns() {
134 [ -e /proc/self/uid_map ] || { echo no; return; }
135 [ "$(wc -l /proc/self/uid_map | awk '{ print $1 }')" -eq 1 ] || \
136 { echo yes; return; }
137 line=$(awk '{ print $1 " " $2 " " $3 }' /proc/self/uid_map)
138 [ "$line" = "0 0 4294967295" ] && { echo no; return; }
139 echo yes
140 }
141
142 relevant_file() {
143 FILE_PATH="${LXC_CACHE_PATH}/$1"
144 if [ -e "${FILE_PATH}-${DOWNLOAD_MODE}" ]; then
145 FILE_PATH="${FILE_PATH}-${DOWNLOAD_MODE}"
146 fi
147 if [ -e "$FILE_PATH.${DOWNLOAD_COMPAT_LEVEL}" ]; then
148 FILE_PATH="${FILE_PATH}.${DOWNLOAD_COMPAT_LEVEL}"
149 fi
150
151 echo $FILE_PATH
152 }
153
154 usage() {
155 cat <<EOF
156 LXC container image downloader
157
158 Required arguments:
159 [ -d | --dist <distribution> ]: The name of the distribution
160 [ -r | --release <release> ]: Release name/version
161 [ -a | --arch <architecture> ]: Architecture of the container
162
163 Optional arguments:
164 [ -h | --help ]: This help message
165 [ -l | --list ]: List all available images
166 [ --variant <variant> ]: Variant of the image (default: "default")
167 [ --server <server> ]: Image server (default: "images.linuxcontainers.org")
168 [ --keyid <keyid> ]: GPG keyid (default: 0x...)
169 [ --keyserver <keyserver> ]: GPG keyserver to use
170 [ --no-validate ]: Disable GPG validation (not recommended)
171 [ --flush-cache ]: Flush the local copy (if present)
172 [ --force-cache ]; Force the use of the local copy even if expired
173
174 LXC internal arguments (do not pass manually!):
175 [ --name <name> ]: The container name
176 [ --path <path> ]: The path to the container
177 [ --rootfs <rootfs> ]: The path to the container's rootfs
178 [ --mapped-uid <map> ]: A uid/gid map (user namespaces)
179 EOF
180 return 0
181 }
182
183 options=$(getopt -o d:r:a:hl -l dist:,release:,arch:,help,list,variant:,\
184 server:,keyid:,no-validate,flush-cache,force-cache,name:,path:,\
185 rootfs:,mapped-uid: -- "$@")
186
187 if [ $? -ne 0 ]; then
188 usage
189 exit 1
190 fi
191 eval set -- "$options"
192
193 while :; do
194 case "$1" in
195 -h|--help) usage && exit 1;;
196 -l|--list) DOWNLOAD_LIST_IMAGES="true"; shift 1;;
197 -d|--dist) DOWNLOAD_DIST=$2; shift 2;;
198 -r|--release) DOWNLOAD_RELEASE=$2; shift 2;;
199 -a|--arch) DOWNLOAD_ARCH=$2; shift 2;;
200 --variant) DOWNLOAD_VARIANT=$2; shift 2;;
201 --server) DOWNLOAD_SERVER=$2; shift 2;;
202 --keyid) DOWNLOAD_KEYID=$2; shift 2;;
203 --no-validate) DOWNLOAD_VALIDATE="false"; shift 1;;
204 --flush-cache) DOWNLOAD_FLUSH_CACHE="true"; shift 1;;
205 --force-cache) DOWNLOAD_FORCE_CACHE="true"; shift 1;;
206 --name) LXC_NAME=$2; shift 2;;
207 --path) LXC_PATH=$2; shift 2;;
208 --rootfs) LXC_ROOTFS=$2; shift 2;;
209 --mapped-uid) LXC_MAPPED_UID=$2; shift 2;;
210 *) break;;
211 esac
212 done
213
214 # Check for required binaries
215 for bin in tar xz wget; do
216 if ! type $bin >/dev/null 2>&1; then
217 echo "ERROR: Missing required tool: $bin" 1>&2
218 exit 1
219 fi
220 done
221
222 # Check for GPG
223 if [ "$DOWNLOAD_VALIDATE" = "true" ]; then
224 if ! type gpg >/dev/null 2>&1; then
225 echo "ERROR: Missing recommended tool: gpg" 1>&2
226 echo "You can workaround this by using --no-validate." 1>&2
227 exit 1
228 fi
229 fi
230
231 # Check that we have all variables we need
232 if [ -z "$LXC_NAME" ] || [ -z "$LXC_PATH" ] || [ -z "$LXC_ROOTFS" ]; then
233 echo "ERROR: Not running through LXC." 1>&2
234 exit 1
235 fi
236
237 if [ "$(in_userns)" = "yes" ]; then
238 if [ -z "$LXC_MAPPED_UID" ] || [ "$LXC_MAPPED_UID" = "-1" ]; then
239 echo "ERROR: In a user namespace without a map." 1>&2
240 exit 1
241 fi
242 DOWNLOAD_MODE="user"
243 fi
244
245 if [ -z "$DOWNLOAD_DIST" ] || [ -z "$DOWNLOAD_RELEASE" ] || \
246 [ -z "$DOWNLOAD_ARCH" ]; then
247 DOWNLOAD_INTERACTIVE="true"
248 fi
249
250 # Trap all exit signals
251 trap cleanup EXIT HUP INT TERM
252 DOWNLOAD_TEMP=$(mktemp -d)
253
254 # Simply list images
255 if [ "$DOWNLOAD_LIST_IMAGES" = "true" ] || \
256 [ "$DOWNLOAD_INTERACTIVE" = "true" ]; then
257 # Initialize GPG
258 gpg_setup
259
260 # Grab the index
261 DOWNLOAD_INDEX_PATH=/meta/1.0/index-${DOWNLOAD_MODE}
262
263 echo "Downloading the image index"
264 if ! download_file ${DOWNLOAD_INDEX_PATH}.${DOWNLOAD_COMPAT_LEVEL} \
265 ${DOWNLOAD_TEMP}/index noexit ||
266 ! download_sig ${DOWNLOAD_INDEX_PATH}.${DOWNLOAD_COMPAT_LEVEL}.asc \
267 ${DOWNLOAD_TEMP}/index.asc noexit; then
268 download_file ${DOWNLOAD_INDEX_PATH} ${DOWNLOAD_TEMP}/index normal
269 download_sig ${DOWNLOAD_INDEX_PATH}.asc \
270 ${DOWNLOAD_TEMP}/index.asc normal
271 fi
272
273 gpg_validate ${DOWNLOAD_TEMP}/index.asc
274
275 # Parse it
276 echo ""
277 echo "---"
278 echo "DIST\tRELEASE\tARCH\tVARIANT\tBUILD"
279 echo "---"
280 while read line; do
281 # Basic CSV parser
282 OLD_IFS=$IFS
283 IFS=";"
284 set -- $line
285 IFS=$OLD_IFS
286
287 [ -n "$DOWNLOAD_DIST" ] && [ "$1" != "$DOWNLOAD_DIST" ] && continue
288 [ -n "$DOWNLOAD_RELEASE" ] && [ "$2" != "$DOWNLOAD_RELEASE" ] && continue
289 [ -n "$DOWNLOAD_ARCH" ] && [ "$3" != "$DOWNLOAD_ARCH" ] && continue
290 [ -n "$DOWNLOAD_VARIANT" ] && [ "$4" != "$DOWNLOAD_VARIANT" ] && continue
291 [ -z "$5" ] || [ -z "$6" ] && continue
292
293 echo "$1\t$2\t$3\t$4\t$5"
294 done < ${DOWNLOAD_TEMP}/index
295 echo "---"
296
297 if [ "$DOWNLOAD_LIST_IMAGES" = "true" ]; then
298 exit 1
299 fi
300
301 # Interactive mode
302 echo ""
303
304 if [ -z "$DOWNLOAD_DIST" ]; then
305 echo -n "Distribution: "
306 read DOWNLOAD_DIST
307 fi
308
309 if [ -z "$DOWNLOAD_RELEASE" ]; then
310 echo -n "Release: "
311 read DOWNLOAD_RELEASE
312 fi
313
314 if [ -z "$DOWNLOAD_ARCH" ]; then
315 echo -n "Architecture: "
316 read DOWNLOAD_ARCH
317 fi
318
319 echo ""
320 fi
321
322 # Setup the cache
323 if [ "$DOWNLOAD_MODE" = "system" ]; then
324 LXC_CACHE_BASE="$LOCALSTATEDIR/cache/"
325 LXC_CACHE_PATH="$LOCALSTATEDIR/cache/lxc/download/$DOWNLOAD_DIST"
326 LXC_CACHE_PATH="$LXC_CACHE_PATH/$DOWNLOAD_RELEASE/$DOWNLOAD_ARCH"
327 else
328 LXC_CACHE_BASE="$HOME/.cache/lxc/"
329 LXC_CACHE_PATH="$HOME/.cache/lxc/download/$DOWNLOAD_DIST"
330 LXC_CACHE_PATH="$LXC_CACHE_PATH/$DOWNLOAD_RELEASE/$DOWNLOAD_ARCH"
331 fi
332
333 if [ -d "$LXC_CACHE_PATH" ]; then
334 if [ "$DOWNLOAD_FLUSH_CACHE" = "true" ]; then
335 echo "Flushing the cache..."
336 rm -Rf $LXC_CACHE_PATH
337 elif [ "$DOWNLOAD_FORCE_CACHE" = "true" ]; then
338 DOWNLOAD_USE_CACHE="true"
339 else
340 DOWNLOAD_USE_CACHE="true"
341 if [ -e "$(relevant_file expiry)" ]; then
342 if [ "$(cat $(relevant_file expiry))" -lt $(date +%s) ]; then
343 echo "The cached copy has expired, re-downloading..."
344 DOWNLOAD_USE_CACHE="false"
345 fi
346 fi
347 fi
348 fi
349
350 # Download what's needed
351 if [ "$DOWNLOAD_USE_CACHE" = "false" ]; then
352 # Initialize GPG
353 gpg_setup
354
355 # Grab the index
356 DOWNLOAD_INDEX_PATH=/meta/1.0/index-${DOWNLOAD_MODE}
357
358 echo "Downloading the image index"
359 if ! download_file ${DOWNLOAD_INDEX_PATH}.${DOWNLOAD_COMPAT_LEVEL} \
360 ${DOWNLOAD_TEMP}/index noexit ||
361 ! download_sig ${DOWNLOAD_INDEX_PATH}.${DOWNLOAD_COMPAT_LEVEL}.asc \
362 ${DOWNLOAD_TEMP}/index.asc noexit; then
363 download_file ${DOWNLOAD_INDEX_PATH} ${DOWNLOAD_TEMP}/index normal
364 download_sig ${DOWNLOAD_INDEX_PATH}.asc \
365 ${DOWNLOAD_TEMP}/index.asc normal
366 fi
367
368 gpg_validate ${DOWNLOAD_TEMP}/index.asc
369
370 # Parse it
371 while read line; do
372 # Basic CSV parser
373 OLD_IFS=$IFS
374 IFS=";"
375 set -- $line
376 IFS=$OLD_IFS
377
378 if [ "$1" != "$DOWNLOAD_DIST" ] || \
379 [ "$2" != "$DOWNLOAD_RELEASE" ] || \
380 [ "$3" != "$DOWNLOAD_ARCH" ] || \
381 [ "$4" != "$DOWNLOAD_VARIANT" ] || \
382 [ -z "$6" ]; then
383 continue
384 fi
385
386 DOWNLOAD_BUILD=$5
387 DOWNLOAD_URL=$6
388 break
389 done < ${DOWNLOAD_TEMP}/index
390
391 if [ -z "$DOWNLOAD_URL" ]; then
392 echo "ERROR: Couldn't find a matching image." 1>&1
393 exit 1
394 fi
395
396 if [ -d "$LXC_CACHE_PATH" ] && [ -f "$LXC_CACHE_PATH/build_id" ] && \
397 [ "$(cat $LXC_CACHE_PATH/build_id)" = "$DOWNLOAD_BUILD" ]; then
398 echo "The cache is already up to date."
399 echo "Using image from local cache"
400 else
401 # Download the actual files
402 echo "Downloading the rootfs"
403 download_file $DOWNLOAD_URL/rootfs.tar.xz \
404 ${DOWNLOAD_TEMP}/rootfs.tar.xz normal
405 download_sig $DOWNLOAD_URL/rootfs.tar.xz.asc \
406 ${DOWNLOAD_TEMP}/rootfs.tar.xz.asc normal
407 gpg_validate ${DOWNLOAD_TEMP}/rootfs.tar.xz.asc
408
409 echo "Downloading the metadata"
410 download_file $DOWNLOAD_URL/meta.tar.xz \
411 ${DOWNLOAD_TEMP}/meta.tar.xz normal
412 download_sig $DOWNLOAD_URL/meta.tar.xz.asc \
413 ${DOWNLOAD_TEMP}/meta.tar.xz.asc normal
414 gpg_validate ${DOWNLOAD_TEMP}/meta.tar.xz.asc
415
416 if [ -d $LXC_CACHE_PATH ]; then
417 rm -Rf $LXC_CACHE_PATH
418 fi
419 mkdir -p $LXC_CACHE_PATH
420 mv ${DOWNLOAD_TEMP}/rootfs.tar.xz $LXC_CACHE_PATH
421 if ! tar Jxf ${DOWNLOAD_TEMP}/meta.tar.xz -C $LXC_CACHE_PATH; then
422 echo "ERROR: Invalid rootfs tarball." 2>&1
423 exit 1
424 fi
425
426 echo $DOWNLOAD_BUILD > $LXC_CACHE_PATH/build_id
427
428 if [ -n "$LXC_MAPPED_UID" ] && [ "$LXC_MAPPED_UID" != "-1" ]; then
429 chown -R $LXC_MAPPED_UID $LXC_CACHE_BASE >/dev/null 2>&1 || true
430 fi
431 echo "The image cache is now ready"
432 fi
433 else
434 echo "Using image from local cache"
435 fi
436
437 # Unpack the rootfs
438 echo "Unpacking the rootfs"
439
440 EXCLUDES=""
441 excludelist=$(relevant_file excludes)
442 if [ -f "${excludelist}" ]; then
443 while read line; do
444 EXCLUDES="$EXCLUDES --exclude=$line"
445 done < $excludelist
446 fi
447
448 tar --anchored ${EXCLUDES} --numeric-owner -xpJf \
449 ${LXC_CACHE_PATH}/rootfs.tar.xz -C ${LXC_ROOTFS}
450
451 mkdir -p ${LXC_ROOTFS}/dev/pts/
452
453 # Setup the configuration
454 configfile=$(relevant_file config)
455 fstab=$(relevant_file fstab)
456 if [ ! -e $configfile ]; then
457 echo "ERROR: meta tarball is missing the configuration file" 1>&2
458 exit 1
459 fi
460
461 ## Extract all the network config entries
462 sed -i -e "/lxc.network/{w ${LXC_PATH}/config-network" -e "d}" \
463 ${LXC_PATH}/config
464
465 ## Extract any other config entry
466 sed -i -e "/lxc./{w ${LXC_PATH}/config-auto" -e "d}" ${LXC_PATH}/config
467
468 ## Append the defaults
469 echo "" >> ${LXC_PATH}/config
470 echo "# Distribution configuration" >> ${LXC_PATH}/config
471 cat $configfile >> ${LXC_PATH}/config
472
473 ## Add the container-specific config
474 echo "" >> ${LXC_PATH}/config
475 echo "# Container specific configuration" >> ${LXC_PATH}/config
476 if [ -e "${LXC_PATH}/config-auto" ]; then
477 cat ${LXC_PATH}/config-auto >> ${LXC_PATH}/config
478 rm ${LXC_PATH}/config-auto
479 fi
480 if [ -e "$fstab" ]; then
481 echo "lxc.mount = ${LXC_PATH}/fstab" >> ${LXC_PATH}/config
482 fi
483 echo "lxc.utsname = ${LXC_NAME}" >> ${LXC_PATH}/config
484
485 ## Re-add the previously removed network config
486 if [ -e "${LXC_PATH}/config-network" ]; then
487 echo "" >> ${LXC_PATH}/config
488 echo "# Network configuration" >> ${LXC_PATH}/config
489 cat ${LXC_PATH}/config-network >> ${LXC_PATH}/config
490 rm ${LXC_PATH}/config-network
491 fi
492
493 TEMPLATE_FILES="${LXC_PATH}/config"
494
495 # Setup the fstab
496 if [ -e $fstab ]; then
497 cp ${fstab} ${LXC_PATH}/fstab
498 TEMPLATE_FILES="$TEMPLATE_FILES ${LXC_PATH}/fstab"
499 fi
500
501 # Look for extra templates
502 if [ -e "$(relevant_file templates)" ]; then
503 while read line; do
504 fullpath=${LXC_ROOTFS}/$line
505 [ ! -e "$fullpath" ] && continue
506 TEMPLATE_FILES="$TEMPLATE_FILES $fullpath"
507 done < $(relevant_file templates)
508 fi
509
510 # Replace variables in all templates
511 for file in $TEMPLATE_FILES; do
512 [ ! -f "$file" ] && continue
513
514 sed -i "s#LXC_NAME#$LXC_NAME#g" $file
515 sed -i "s#LXC_PATH#$LXC_PATH#g" $file
516 sed -i "s#LXC_ROOTFS#$LXC_ROOTFS#g" $file
517 sed -i "s#LXC_TEMPLATE_CONFIG#$LXC_TEMPLATE_CONFIG#g" $file
518 sed -i "s#LXC_HOOK_DIR#$LXC_HOOK_DIR#g" $file
519 done
520
521 if [ -n "$LXC_MAPPED_UID" ] && [ "$LXC_MAPPED_UID" != "-1" ]; then
522 chown $LXC_MAPPED_UID $LXC_PATH/config $LXC_PATH/fstab >/dev/null 2>&1 || true
523 fi
524
525 if [ -e "$(relevant_file create-message)" ]; then
526 echo ""
527 echo "---"
528 cat "$(relevant_file create-message)"
529 fi
530
531 exit 0