]> git.proxmox.com Git - mirror_lxc.git/blame - templates/lxc-oci.in
Merge pull request #4242 from mihalicyn/fixes_after_coverity
[mirror_lxc.git] / templates / lxc-oci.in
CommitLineData
0ef43a5c
SH
1#!/bin/bash
2
3# Create application containers from OCI images
4
5# Copyright © 2014 Stéphane Graber <stgraber@ubuntu.com>
6# Copyright © 2017 Serge Hallyn <serge@hallyn.com>
7#
8# This library is free software; you can redistribute it and/or
9# modify it under the terms of the GNU Lesser General Public
10# License as published by the Free Software Foundation; either
11# version 2.1 of the License, or (at your option) any later version.
12
13# This library is distributed in the hope that it will be useful,
14# but WITHOUT ANY WARRANTY; without even the implied warranty of
15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16# Lesser General Public License for more details.
17
18# You should have received a copy of the GNU Lesser General Public
19# License along with this library; if not, write to the Free Software
20# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
21# USA
22
23set -eu
0ef43a5c
SH
24
25# Make sure the usual locations are in PATH
655d10ed 26export PATH="$PATH:/usr/sbin:/usr/bin:/sbin:/bin"
0ef43a5c
SH
27
28# Check for required binaries
29for bin in skopeo umoci jq; do
7a767165 30 if ! command -v $bin >/dev/null 2>&1; then
4328e9e3
CB
31 echo "ERROR: Missing required tool: $bin" 1>&2
32 exit 1
33 fi
0ef43a5c
SH
34done
35
3cc0ec84
CB
36LOCALSTATEDIR=@LOCALSTATEDIR@
37LXC_TEMPLATE_CONFIG=@LXCTEMPLATECONFIG@
38LXC_HOOK_DIR=@LXCHOOKDIR@
d7c685c6 39
0ef43a5c
SH
40# Some useful functions
41cleanup() {
4328e9e3
CB
42 if [ -d "${DOWNLOAD_TEMP}" ]; then
43 rm -Rf "${DOWNLOAD_TEMP}"
44 fi
45
46 if [ -d "${LXC_ROOTFS}.tmp" ]; then
47 rm -Rf "${LXC_ROOTFS}.tmp"
48 fi
0ef43a5c
SH
49}
50
51in_userns() {
4328e9e3
CB
52 [ -e /proc/self/uid_map ] || { echo no; return; }
53 while read -r line; do
54 fields="$(echo "$line" | awk '{ print $1 " " $2 " " $3 }')"
55 if [ "${fields}" = "0 0 4294967295" ]; then
56 echo no;
57 return;
58 fi
59 if echo "${fields}" | grep -q " 0 1$"; then
60 echo userns-root;
61 return;
62 fi
63 done < /proc/self/uid_map
64
16a312e1
LP
65 if [ -e /proc/1/uid_map ]; then
66 if [ "$(cat /proc/self/uid_map)" = "$(cat /proc/1/uid_map)" ]; then
67 echo userns-root
68 return
69 fi
70 fi
4328e9e3 71 echo yes
0ef43a5c
SH
72}
73
ce59e4ca 74getconfigpath() {
4328e9e3
CB
75 basedir="$1"
76 q="$2"
77
78 digest=$(jq -c -r --arg q "$q" '.manifests[] | if .annotations."org.opencontainers.image.ref.name" == $q then .digest else empty end' < "${basedir}/index.json")
79 if [ -z "${digest}" ]; then
80 echo "$q not found in index.json" >&2
81 return
82 fi
83
84 # Ok we have the image config digest, now get the config from that
85 # shellcheck disable=SC2039
86 d=${digest:7}
87 cdigest=$(jq -c -r '.config.digest' < "${basedir}/blobs/sha256/${d}")
88 if [ -z "${cdigest}" ]; then
89 echo "container config not found" >&2
90 return
91 fi
92
93 # shellcheck disable=SC2039
94 d2=${cdigest:7}
95 echo "${basedir}/blobs/sha256/${d2}"
96 return
ce59e4ca
FA
97}
98
4328e9e3 99# Get entrypoint from oci image. Use sh if unspecified
ce59e4ca 100getep() {
4328e9e3
CB
101 if [ "$#" -eq 0 ]; then
102 echo "/bin/sh"
103 return
104 fi
105
106 configpath="$1"
107
d99e3b2e
FA
108 ep=$(jq -c '.config.Entrypoint[]?'< "${configpath}" | tr '\n' ' ')
109 cmd=$(jq -c '.config.Cmd[]?'< "${configpath}" | tr '\n' ' ')
4328e9e3
CB
110 if [ -z "${ep}" ]; then
111 ep="${cmd}"
112 if [ -z "${ep}" ]; then
113 ep="/bin/sh"
114 fi
115 elif [ -n "${cmd}" ]; then
116 ep="${ep} ${cmd}"
117 fi
118
119 echo "${ep}"
120 return
0ef43a5c
SH
121}
122
996202e7
FA
123# get environment from oci image.
124getenv() {
4328e9e3
CB
125 if [ "$#" -eq 0 ]; then
126 return
127 fi
996202e7 128
4328e9e3 129 configpath="$1"
996202e7 130
4328e9e3 131 env=$(jq -c -r '.config.Env[]'< "${configpath}")
996202e7 132
4328e9e3
CB
133 echo "${env}"
134 return
996202e7
FA
135}
136
b8cfbbd1
JS
137# check var is decimal.
138isdecimal() {
139 var="$1"
140 if [ "${var}" -eq "${var}" ] 2> /dev/null; then
141 return 0
142 else
143 return 1
4328e9e3 144 fi
b8cfbbd1 145}
e86dcc91 146
b8cfbbd1
JS
147# get uid, gid from oci image.
148getuidgid() {
4328e9e3 149 configpath="$1"
b8cfbbd1
JS
150 rootpath="$2"
151 passwdpath="${rootpath}/etc/passwd"
152 grouppath="${rootpath}/etc/group"
e86dcc91 153
b8cfbbd1 154 usergroup=$(jq -c -r '.config.User' < "${configpath}")
4328e9e3 155 # shellcheck disable=SC2039
b8cfbbd1
JS
156 usergroup=(${usergroup//:/ })
157
158 user=${usergroup[0]:-0}
a2ade420
JS
159 if ! isdecimal "${user}"; then
160 if [ -f ${passwdpath} ]; then
161 user=$(grep "^${user}:" "${passwdpath}" | awk -F: '{print $3}')
162 else
163 user=0
164 fi
b8cfbbd1
JS
165 fi
166
167 group=${usergroup[1]:-}
a2ade420
JS
168 if [ -z "${group}" ]; then
169 if [ -f "${passwdpath}" ]; then
170 group=$(grep "^[^:]*:[^:]*:${user}:" "${passwdpath}" | awk -F: '{print $4}')
171 else
172 group=0
173 fi
174 elif ! isdecimal "${group}"; then
175 if [ -f "${grouppath}" ]; then
176 group=$(grep "^${group}:" "${grouppath}" | awk -F: '{print $3}')
177 else
178 group=0
179 fi
b8cfbbd1 180 fi
e86dcc91 181
b8cfbbd1 182 echo "${user:-0} ${group:-0}"
4328e9e3 183 return
e86dcc91
FA
184}
185
a787c332
FA
186# get cwd from oci image.
187getcwd() {
4328e9e3
CB
188 if [ "$#" -eq 0 ]; then
189 echo "/"
190 return
191 fi
a787c332 192
4328e9e3 193 configpath="$1"
a787c332 194
4328e9e3 195 cwd=$(jq -c -r '.config.WorkingDir // "/"' < "${configpath}")
a787c332 196
4328e9e3
CB
197 echo "${cwd}"
198 return
a787c332
FA
199}
200
0ef43a5c 201usage() {
4328e9e3 202 cat <<EOF
0ef43a5c
SH
203LXC container template for OCI images
204
205Special arguments:
206[ -h | --help ]: Print this help message and exit.
207
208Required arguments:
209[ -u | --url <url> ]: The OCI image URL
210
797f99c6
FA
211Optional arguments:
212[ --username <username> ]: The username for the registry
213[ --password <password> ]: The password for the registry
214
0ef43a5c
SH
215LXC internal arguments (do not pass manually!):
216[ --name <name> ]: The container name
217[ --path <path> ]: The path to the container
218[ --rootfs <rootfs> ]: The path to the container's rootfs
219[ --mapped-uid <map> ]: A uid map (user namespaces)
220[ --mapped-gid <map> ]: A gid map (user namespaces)
0ef43a5c 221EOF
4328e9e3 222 return 0
0ef43a5c
SH
223}
224
4328e9e3 225if ! options=$(getopt -o u:h -l help,url:,username:,password:,no-cache,dhcp,name:,path:,rootfs:,mapped-uid:,mapped-gid: -- "$@"); then
0ef43a5c
SH
226 usage
227 exit 1
228fi
229eval set -- "$options"
230
231OCI_URL=""
797f99c6
FA
232OCI_USERNAME=
233OCI_PASSWORD=
52e31c07 234OCI_USE_CACHE="true"
9a962dc6 235OCI_USE_DHCP="false"
797f99c6 236
0ef43a5c
SH
237LXC_MAPPED_GID=
238LXC_MAPPED_UID=
239LXC_NAME=
240LXC_PATH=
241LXC_ROOTFS=
242
243while :; do
4328e9e3
CB
244 case "$1" in
245 -h|--help) usage && exit 1;;
246 -u|--url) OCI_URL=$2; shift 2;;
247 --username) OCI_USERNAME=$2; shift 2;;
248 --password) OCI_PASSWORD=$2; shift 2;;
249 --no-cache) OCI_USE_CACHE="false"; shift 1;;
250 --dhcp) OCI_USE_DHCP="true"; shift 1;;
251 --name) LXC_NAME=$2; shift 2;;
252 --path) LXC_PATH=$2; shift 2;;
253 --rootfs) LXC_ROOTFS=$2; shift 2;;
254 --mapped-uid) LXC_MAPPED_UID=$2; shift 2;;
255 --mapped-gid) LXC_MAPPED_GID=$2; shift 2;;
256 *) break;;
257 esac
0ef43a5c
SH
258done
259
260# Check that we have all variables we need
261if [ -z "$LXC_NAME" ] || [ -z "$LXC_PATH" ] || [ -z "$LXC_ROOTFS" ]; then
4328e9e3
CB
262 echo "ERROR: Not running through LXC" 1>&2
263 exit 1
0ef43a5c
SH
264fi
265
266if [ -z "$OCI_URL" ]; then
4328e9e3
CB
267 echo "ERROR: no OCI URL given"
268 exit 1
0ef43a5c
SH
269fi
270
797f99c6 271if [ -n "$OCI_PASSWORD" ] && [ -z "$OCI_USERNAME" ]; then
4328e9e3
CB
272 echo "ERROR: password given but no username specified"
273 exit 1
797f99c6
FA
274fi
275
52e31c07 276if [ "${OCI_USE_CACHE}" = "true" ]; then
4328e9e3
CB
277 if ! skopeo copy --help | grep -q 'dest-shared-blob-dir'; then
278 echo "INFO: skopeo doesn't support blob caching"
279 OCI_USE_CACHE="false"
280 fi
52e31c07
FA
281fi
282
0ef43a5c
SH
283USERNS=$(in_userns)
284
52e31c07 285if [ "$USERNS" = "yes" ]; then
4328e9e3
CB
286 if [ -z "$LXC_MAPPED_UID" ] || [ "$LXC_MAPPED_UID" = "-1" ]; then
287 echo "ERROR: In a user namespace without a map" 1>&2
288 exit 1
289 fi
52e31c07
FA
290fi
291
292if [ "${OCI_USE_CACHE}" = "true" ]; then
4328e9e3
CB
293 if [ "$USERNS" = "yes" ]; then
294 DOWNLOAD_BASE="${HOME}/.cache/lxc"
295 else
296 DOWNLOAD_BASE="${LOCALSTATEDIR}/cache/lxc"
297 fi
52e31c07 298else
4328e9e3 299 DOWNLOAD_BASE=/tmp
0ef43a5c 300fi
8c7536ec 301mkdir -p "${DOWNLOAD_BASE}"
0ef43a5c
SH
302
303# Trap all exit signals
304trap cleanup EXIT HUP INT TERM
305
7a767165 306if ! command -v mktemp >/dev/null 2>&1; then
4328e9e3
CB
307 DOWNLOAD_TEMP="${DOWNLOAD_BASE}/lxc-oci.$$"
308 mkdir -p "${DOWNLOAD_TEMP}"
0ef43a5c 309else
4328e9e3 310 DOWNLOAD_TEMP=$(mktemp -d -p "${DOWNLOAD_BASE}")
0ef43a5c
SH
311fi
312
52e31c07 313# Download the image
4328e9e3 314# shellcheck disable=SC2039
797f99c6
FA
315skopeo_args=("")
316if [ -n "$OCI_USERNAME" ]; then
4328e9e3
CB
317 CREDENTIALS="${OCI_USERNAME}"
318
319 if [ -n "$OCI_PASSWORD" ]; then
320 CREDENTIALS="${CREDENTIALS}:${OCI_PASSWORD}"
321 fi
322
323 # shellcheck disable=SC2039
324 skopeo_args+=(--src-creds "${CREDENTIALS}")
797f99c6 325fi
4328e9e3 326
52e31c07 327if [ "${OCI_USE_CACHE}" = "true" ]; then
4328e9e3
CB
328 # shellcheck disable=SC2039
329 # shellcheck disable=SC2068
330 skopeo_args+=(--dest-shared-blob-dir "${DOWNLOAD_BASE}")
331 # shellcheck disable=SC2039
332 # shellcheck disable=SC2068
333 skopeo copy ${skopeo_args[@]} "${OCI_URL}" "oci:${DOWNLOAD_TEMP}:latest"
334 ln -s "${DOWNLOAD_BASE}/sha256" "${DOWNLOAD_TEMP}/blobs/sha256"
52e31c07 335else
4328e9e3
CB
336 # shellcheck disable=SC2039
337 # shellcheck disable=SC2068
338 skopeo copy ${skopeo_args[@]} "${OCI_URL}" "oci:${DOWNLOAD_TEMP}:latest"
52e31c07 339fi
0ef43a5c 340
0ef43a5c 341echo "Unpacking the rootfs"
4328e9e3 342# shellcheck disable=SC2039
51c80577
FA
343umoci_args=("")
344if [ -n "$LXC_MAPPED_UID" ] && [ "$LXC_MAPPED_UID" != "-1" ]; then
4328e9e3
CB
345 # shellcheck disable=SC2039
346 umoci_args+=(--rootless)
51c80577 347fi
4328e9e3
CB
348# shellcheck disable=SC2039
349# shellcheck disable=SC2068
845ba283 350umoci --log=error unpack ${umoci_args[@]} --image "${DOWNLOAD_TEMP}:latest" "${LXC_ROOTFS}.tmp"
bbd84ff1 351find "${LXC_ROOTFS}.tmp/rootfs" -mindepth 1 -maxdepth 1 -exec mv '{}' "${LXC_ROOTFS}/" \;
0ef43a5c 352
4328e9e3 353OCI_CONF_FILE=$(getconfigpath "${DOWNLOAD_TEMP}" latest)
0ef43a5c 354LXC_CONF_FILE="${LXC_PATH}/config"
4328e9e3 355entrypoint=$(getep "${OCI_CONF_FILE}")
3dca1af0 356echo "lxc.execute.cmd = '${entrypoint}'" >> "${LXC_CONF_FILE}"
0ef43a5c
SH
357echo "lxc.mount.auto = proc:mixed sys:mixed cgroup:mixed" >> "${LXC_CONF_FILE}"
358
4328e9e3
CB
359environment=$(getenv "${OCI_CONF_FILE}")
360# shellcheck disable=SC2039
996202e7 361while read -r line; do
4328e9e3 362 echo "lxc.environment = ${line}" >> "${LXC_CONF_FILE}"
996202e7
FA
363done <<< "${environment}"
364
d7c685c6 365if [ -e "${LXC_TEMPLATE_CONFIG}/common.conf" ]; then
4328e9e3 366 echo "lxc.include = ${LXC_TEMPLATE_CONFIG}/common.conf" >> "${LXC_CONF_FILE}"
d7c685c6
FA
367fi
368
369if [ -n "$LXC_MAPPED_UID" ] && [ "$LXC_MAPPED_UID" != "-1" ] && [ -e "${LXC_TEMPLATE_CONFIG}/userns.conf" ]; then
4328e9e3 370 echo "lxc.include = ${LXC_TEMPLATE_CONFIG}/userns.conf" >> "${LXC_CONF_FILE}"
d7c685c6
FA
371fi
372
1689c7cf 373if [ -e "${LXC_TEMPLATE_CONFIG}/oci.common.conf" ]; then
4328e9e3 374 echo "lxc.include = ${LXC_TEMPLATE_CONFIG}/oci.common.conf" >> "${LXC_CONF_FILE}"
1689c7cf
JC
375fi
376
9a962dc6 377if [ "${OCI_USE_DHCP}" = "true" ]; then
4328e9e3
CB
378 echo "lxc.hook.start-host = ${LXC_HOOK_DIR}/dhclient" >> "${LXC_CONF_FILE}"
379 echo "lxc.hook.stop = ${LXC_HOOK_DIR}/dhclient" >> "${LXC_CONF_FILE}"
9a962dc6
JC
380fi
381
bc2c91ae
FA
382echo "lxc.uts.name = ${LXC_NAME}" >> "${LXC_CONF_FILE}"
383# set the hostname
4328e9e3 384cat <<EOF > "${LXC_ROOTFS}/etc/hostname"
bc2c91ae
FA
385${LXC_NAME}
386EOF
0ef43a5c 387
b5236550 388# set minimal hosts
4328e9e3 389cat <<EOF > "${LXC_ROOTFS}/etc/hosts"
b5236550
FA
390127.0.0.1 localhost
391127.0.1.1 ${LXC_NAME}
8f54d926
FA
392::1 localhost ip6-localhost ip6-loopback
393fe00::0 ip6-localnet
394ff00::0 ip6-mcastprefix
395ff02::1 ip6-allnodes
396ff02::2 ip6-allrouters
b5236550
FA
397EOF
398
4328e9e3 399# shellcheck disable=SC2039
b8cfbbd1 400uidgid=($(getuidgid "${OCI_CONF_FILE}" "${LXC_ROOTFS}" ))
4328e9e3 401# shellcheck disable=SC2039
e86dcc91 402echo "lxc.init.uid = ${uidgid[0]}" >> "${LXC_CONF_FILE}"
4328e9e3 403# shellcheck disable=SC2039
e86dcc91
FA
404echo "lxc.init.gid = ${uidgid[1]}" >> "${LXC_CONF_FILE}"
405
4328e9e3 406cwd=$(getcwd "${OCI_CONF_FILE}")
a787c332
FA
407echo "lxc.init.cwd = ${cwd}" >> "${LXC_CONF_FILE}"
408
0ef43a5c 409if [ -n "$LXC_MAPPED_UID" ] && [ "$LXC_MAPPED_UID" != "-1" ]; then
4328e9e3 410 chown "$LXC_MAPPED_UID" "$LXC_PATH/config" "$LXC_PATH/fstab" > /dev/null 2>&1 || true
0ef43a5c
SH
411fi
412if [ -n "$LXC_MAPPED_GID" ] && [ "$LXC_MAPPED_GID" != "-1" ]; then
4328e9e3 413 chgrp "$LXC_MAPPED_GID" "$LXC_PATH/config" "$LXC_PATH/fstab" > /dev/null 2>&1 || true
0ef43a5c
SH
414fi
415
416exit 0