]> git.proxmox.com Git - mirror_lxc.git/blame - templates/lxc-download.in
lxc-download: Fix wrong option parsing
[mirror_lxc.git] / templates / lxc-download.in
CommitLineData
71d3a659
SG
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
22set -eu
23
24LXC_TEMPLATE_CONFIG="@LXCTEMPLATECONFIG@"
25LXC_HOOK_DIR="@LXCHOOKDIR@"
26LOCALSTATEDIR="@LOCALSTATEDIR@"
27
28# Defaults
29DOWNLOAD_DIST=
30DOWNLOAD_RELEASE=
31DOWNLOAD_ARCH=
32DOWNLOAD_VARIANT="default"
33DOWNLOAD_SERVER="images.linuxcontainers.org"
34DOWNLOAD_KEYID="0xBAEFF88C22F6E216"
35DOWNLOAD_KEYSERVER="pool.sks-keyservers.net"
36DOWNLOAD_VALIDATE="true"
37DOWNLOAD_FLUSH_CACHE="false"
38DOWNLOAD_MODE="system"
39DOWNLOAD_USE_CACHE="false"
40DOWNLOAD_URL=
41DOWNLOAD_SHOW_HTTP_WARNING="true"
42DOWNLOAD_SHOW_GPG_WARNING="true"
43DOWNLOAD_COMPAT_LEVEL=1
10a5fab6 44DOWNLOAD_LIST_IMAGES="false"
9accc2ef 45DOWNLOAD_BUILD=
71d3a659
SG
46
47LXC_NAME=
48LXC_PATH=
49LXC_ROOTFS=
50LXC_MAPPED_UID=
51
52# Some useful functions
53cleanup() {
54 if [ -d "$DOWNLOAD_TEMP" ]; then
55 rm -Rf $DOWNLOAD_TEMP
56 fi
57}
58
59download_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
fad96766 65 echo "ERROR: Failed to download http://${DOWNLOAD_SERVER}/$1" 1>&2
71d3a659
SG
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
fad96766 77download_sig() {
33aa351a
SG
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
fad96766
DE
89 fi
90}
91
71d3a659
SG
92gpg_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
109gpg_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
124in_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
133relevant_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
145usage() {
146 cat <<EOF
147LXC container image downloader
148
149Required 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
71d3a659
SG
153
154Optional arguments:
10a5fab6
SG
155[ -h | --help ]: This help message
156[ -l | --list ]: List all available images
71d3a659
SG
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)
9accc2ef 163[ --force-cache ]; Force the use of the local copy even if expired
71d3a659
SG
164
165LXC 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)
170EOF
171 return 0
172}
173
10a5fab6 174options=$(getopt -o d:r:a:hl -l dist:,release:,arch:,help,list,variant:,\
c1becef2 175server:,keyid:,no-validate,flush-cache,force-cache,name:,path:,\
9accc2ef 176rootfs:,mapped-uid: -- "$@")
71d3a659
SG
177
178if [ $? -ne 0 ]; then
179 usage
180 exit 1
181fi
182eval set -- "$options"
183
184while :; do
185 case "$1" in
10a5fab6
SG
186 -h|--help) usage && exit 1;;
187 -l|--list) DOWNLOAD_LIST_IMAGES="true"; shift 1;;
71d3a659
SG
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;;
9accc2ef 196 --force-cache) DOWNLOAD_FORCE_CACHE="true"; shift 1;;
71d3a659
SG
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
203done
204
205# Check for required binaries
206for 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
211done
212
213# Check for GPG
214if [ "$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
220fi
221
222# Check that we have all variables we need
223if [ -z "$LXC_NAME" ] || [ -z "$LXC_PATH" ] || [ -z "$LXC_ROOTFS" ]; then
224 echo "ERROR: Not running through LXC." 1>&2
225 exit 1
226fi
227
228if [ "$(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"
234fi
235
10a5fab6
SG
236if ([ -z "$DOWNLOAD_DIST" ] || [ -z "$DOWNLOAD_RELEASE" ] || \
237 [ -z "$DOWNLOAD_ARCH" ]) && [ "$DOWNLOAD_LIST_IMAGES" = "false" ]; then
71d3a659
SG
238 echo "ERROR: Missing required argument" 1>&2
239 usage
240 exit 1
241fi
242
243# Trap all exit signals
244trap cleanup EXIT HUP INT TERM
245DOWNLOAD_TEMP=$(mktemp -d)
246
10a5fab6
SG
247# Simply list images
248if [ "$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
290fi
291
71d3a659
SG
292# Setup the cache
293if [ "$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"
297else
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"
301fi
302
303if [ -d "$LXC_CACHE_PATH" ]; then
304 if [ "$DOWNLOAD_FLUSH_CACHE" = "true" ]; then
305 echo "Flushing the cache..."
306 rm -Rf $LXC_CACHE_PATH
9accc2ef
SG
307 elif [ "$DOWNLOAD_FORCE_CACHE" = "true" ]; then
308 DOWNLOAD_USE_CACHE="true"
71d3a659
SG
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"
71d3a659
SG
315 fi
316 fi
317 fi
318fi
319
320# Download what's needed
321if [ "$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 ||
33aa351a 331 ! download_sig ${DOWNLOAD_INDEX_PATH}.${DOWNLOAD_COMPAT_LEVEL}.asc \
71d3a659
SG
332 ${DOWNLOAD_TEMP}/index.asc noexit; then
333 download_file ${DOWNLOAD_INDEX_PATH} ${DOWNLOAD_TEMP}/index normal
fad96766 334 download_sig ${DOWNLOAD_INDEX_PATH}.asc \
33aa351a 335 ${DOWNLOAD_TEMP}/index.asc normal
71d3a659
SG
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
9accc2ef 356 DOWNLOAD_BUILD=$5
71d3a659
SG
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
9accc2ef
SG
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
71d3a659 395
9accc2ef
SG
396 echo $DOWNLOAD_BUILD > $LXC_CACHE_PATH/build_id
397
398 if [ -n "$LXC_MAPPED_UID" ] && [ "$LXC_MAPPED_UID" != "-1" ]; then
0d656b05 399 chown -R $LXC_MAPPED_UID $LXC_CACHE_BASE >/dev/null 2>&1 || true
9accc2ef
SG
400 fi
401 echo "The image cache is now ready"
71d3a659 402 fi
71d3a659
SG
403else
404 echo "Using image from local cache"
405fi
406
407# Unpack the rootfs
408echo "Unpacking the rootfs"
fecf101c
SG
409
410EXCLUDES=""
411excludelist=$(relevant_file excludes)
412if [ -f "${excludelist}" ]; then
413 while read line; do
414 EXCLUDES="$EXCLUDES --exclude=$line"
415 done < $excludelist
71d3a659
SG
416fi
417
fecf101c
SG
418tar --anchored ${EXCLUDES} --numeric-owner -xpJf \
419 ${LXC_CACHE_PATH}/rootfs.tar.xz -C ${LXC_ROOTFS}
420
421mkdir -p ${LXC_ROOTFS}/dev/pts/
422
71d3a659
SG
423# Setup the configuration
424configfile=$(relevant_file config)
425fstab=$(relevant_file fstab)
426if [ ! -e $configfile ]; then
427 echo "ERROR: meta tarball is missing the configuration file" 1>&2
428 exit 1
429fi
430
431## Extract all the network config entries
432sed -i -e "/lxc.network/{w ${LXC_PATH}/config-network" -e "d}" \
433 ${LXC_PATH}/config
434
435## Extract any other config entry
436sed -i -e "/lxc./{w ${LXC_PATH}/config-auto" -e "d}" ${LXC_PATH}/config
437
438## Append the defaults
439echo "" >> ${LXC_PATH}/config
440echo "# Distribution configuration" >> ${LXC_PATH}/config
441cat $configfile >> ${LXC_PATH}/config
442
443## Add the container-specific config
444echo "" >> ${LXC_PATH}/config
445echo "# Container specific configuration" >> ${LXC_PATH}/config
446if [ -e "${LXC_PATH}/config-auto" ]; then
447 cat ${LXC_PATH}/config-auto >> ${LXC_PATH}/config
448 rm ${LXC_PATH}/config-auto
449fi
450if [ -e "$fstab" ]; then
451 echo "lxc.mount = ${LXC_PATH}/fstab" >> ${LXC_PATH}/config
452fi
453echo "lxc.utsname = ${LXC_NAME}" >> ${LXC_PATH}/config
454
455## Re-add the previously removed network config
456if [ -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
461fi
462
463TEMPLATE_FILES="${LXC_PATH}/config"
464
465# Setup the fstab
466if [ -e $fstab ]; then
467 cp ${fstab} ${LXC_PATH}/fstab
468 TEMPLATE_FILES="$TEMPLATE_FILES ${LXC_PATH}/fstab"
469fi
470
471# Look for extra templates
472if [ -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)
478fi
479
480# Replace variables in all templates
481for file in $TEMPLATE_FILES; do
fad96766 482 [ ! -f "$file" ] && continue
71d3a659
SG
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
489done
490
491if [ -n "$LXC_MAPPED_UID" ] && [ "$LXC_MAPPED_UID" != "-1" ]; then
0d656b05 492 chown $LXC_MAPPED_UID $LXC_PATH/config $LXC_PATH/fstab >/dev/null 2>&1 || true
71d3a659
SG
493fi
494
495if [ -e "$(relevant_file create-message)" ]; then
496 echo ""
497 echo "---"
498 cat "$(relevant_file create-message)"
499fi
500
501exit 0