]> git.proxmox.com Git - mirror_lxc.git/blame - templates/lxc-alpine.in
Merge pull request #2057 from brauner/2017-12-22/bugfixes
[mirror_lxc.git] / templates / lxc-alpine.in
CommitLineData
04fa4e12 1#!/bin/sh
6515faa1 2# vim: set ts=4:
5845ac2b
JJ
3
4# Exit on error and treat unset variables as an error.
5set -eu
6515faa1
JJ
6
7#
8# LXC template for Alpine Linux 3+
9#
10
11# Note: Do not replace tabs with spaces, it would break heredocs!
12
13# Authors:
14# Jakub Jirutka <jakub@jirutka.cz>
15
16# This library is free software; you can redistribute it and/or
17# modify it under the terms of the GNU Lesser General Public
18# License as published by the Free Software Foundation; either
19# version 2.1 of the License, or (at your option) any later version.
20#
21# This library is distributed in the hope that it will be useful,
22# but WITHOUT ANY WARRANTY; without even the implied warranty of
23# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
24# Lesser General Public License for more details.
25#
26# You should have received a copy of the GNU Lesser General Public
27# License along with this library; if not, write to the Free Software
28# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
2a9a0a08 29
6515faa1
JJ
30
31#=========================== Constants ============================#
8ec981fc 32
207bf0e4 33# Make sure the usual locations are in PATH
6515faa1
JJ
34export PATH=$PATH:/usr/sbin:/usr/bin:/sbin:/bin
35
36readonly LOCAL_STATE_DIR='@LOCALSTATEDIR@'
37readonly LXC_TEMPLATE_CONFIG='@LXCTEMPLATECONFIG@'
38readonly LXC_CACHE_DIR="${LXC_CACHE_PATH:-"$LOCAL_STATE_DIR/cache/lxc"}/alpine"
207bf0e4 39
6515faa1
JJ
40# SHA256 checksums of GPG keys for APK.
41readonly APK_KEYS_SHA256="\
429c102bcc376af1498d549b77bdbfa815ae86faa1d2d82f040e616b18ef2df2d4 alpine-devel@lists.alpinelinux.org-4a6a0840.rsa.pub
6460d3c5
NC
432adcf7ce224f476330b5360ca5edb92fd0bf91c92d83292ed028d7c4e26333ab alpine-devel@lists.alpinelinux.org-4d07755e.rsa.pub
44ebf31683b56410ecc4c00acd9f6e2839e237a3b62b5ae7ef686705c7ba0396a9 alpine-devel@lists.alpinelinux.org-5243ef4b.rsa.pub
451bb2a846c0ea4ca9d0e7862f970863857fc33c32f5506098c636a62a726a847b alpine-devel@lists.alpinelinux.org-524d27bb.rsa.pub
2933a5ce
BL
4612f899e55a7691225603d6fb3324940fc51cd7f133e7ead788663c2b7eecb00c alpine-devel@lists.alpinelinux.org-5261cecb.rsa.pub
4773867d92083f2f8ab899a26ccda7ef63dfaa0032a938620eda605558958a8041 alpine-devel@lists.alpinelinux.org-58199dcc.rsa.pub
489a4cd858d9710963848e6d5f555325dc199d1c952b01cf6e64da2c15deedbd97 alpine-devel@lists.alpinelinux.org-58cbb476.rsa.pub
49780b3ed41786772cbc7b68136546fa3f897f28a23b30c72dde6225319c44cfff alpine-devel@lists.alpinelinux.org-58e4f17d.rsa.pub"
6460d3c5 50
04fa4e12 51readonly APK_KEYS_URI='http://alpinelinux.org/keys'
72ead1c0 52readonly DEFAULT_MIRROR_URL='http://dl-cdn.alpinelinux.org/alpine'
6515faa1
JJ
53
54: ${APK_KEYS_DIR:=/etc/apk/keys}
5845ac2b 55if ! ls "$APK_KEYS_DIR"/alpine* >/dev/null 2>&1; then
6515faa1
JJ
56 APK_KEYS_DIR="$LXC_CACHE_DIR/bootstrap/keys"
57fi
58readonly APK_KEYS_DIR
59
60: ${APK:=$(command -v apk || true)}
61if [ ! -x "$APK" ]; then
62 APK="$LXC_CACHE_DIR/bootstrap/sbin/apk.static"
63fi
64readonly APK
65
66
67#======================== Helper Functions ========================#
569bee5c 68
6515faa1
JJ
69usage() {
70 cat <<-EOF
71 Template specific options can be passed to lxc-create after a '--' like this:
72
1125e053
JJ
73 lxc-create --name=NAME [lxc-create-options] -- [template-options] [PKG...]
74
75 PKG Additional APK package(s) to install into the container.
6515faa1
JJ
76
77 Template options:
78 -a ARCH, --arch=ARCH The container architecture (e.g. x86, x86_64); defaults
79 to the host arch.
04fa4e12 80 -d, --debug Run this script in a debug mode (set -x and wget w/o -q).
6515faa1 81 -F, --flush-cache Remove cached files before build.
72ead1c0 82 -m URL --mirror=URL The Alpine mirror to use; defaults to $DEFAULT_MIRROR_URL.
6515faa1
JJ
83 -r VER, --release=VER The Alpine release branch to install; default is the
84 latest stable.
85
86 Environment variables:
87 APK The apk-tools binary to use when building rootfs. If not set
88 or not executable and apk is not on PATH, then the script
89 will download the latest apk-tools-static.
90 APK_KEYS_DIR Path to directory with GPG keys for APK. If not set and
91 /etc/apk/keys does not contain alpine keys, then the script
92 will download the keys from ${APK_KEYS_URI}.
20f39db7
JJ
93 LXC_CACHE_PATH Path to the cache directory where to store bootstrap files
94 and APK packages.
6515faa1 95 EOF
569bee5c
NC
96}
97
6515faa1
JJ
98die() {
99 local retval=$1; shift
100
5845ac2b 101 printf 'ERROR: %s\n' "$@" 1>&2
6515faa1 102 exit $retval
2a9a0a08
NC
103}
104
6515faa1 105einfo() {
5845ac2b 106 printf "\n==> $1\n"
6515faa1 107}
691ac4a3 108
6515faa1
JJ
109fetch() {
110 if [ "$DEBUG" = 'yes' ]; then
04fa4e12 111 wget -T 10 -O - $@
6515faa1 112 else
04fa4e12 113 wget -T 10 -O - -q $@
6515faa1
JJ
114 fi
115}
2a9a0a08 116
6515faa1
JJ
117latest_release_branch() {
118 local arch="$1"
04fa4e12 119 local branch=$(fetch "$MIRROR_URL/latest-stable/releases/$arch/latest-releases.yaml" \
6515faa1
JJ
120 | sed -En 's/^[ \t]*branch: (.*)$/\1/p' \
121 | head -n 1)
122 [ -n "$branch" ] && echo "$branch"
123}
2a9a0a08 124
6515faa1
JJ
125parse_arch() {
126 case "$1" in
127 x86 | i[3-6]86) echo 'x86';;
128 x86_64 | amd64) echo 'x86_64';;
12d233d5
CL
129 aarch64 | arm64) echo 'aarch64';;
130 armv7) echo 'armv7';;
6515faa1 131 arm*) echo 'armhf';;
2933a5ce 132 ppc64le) echo 'ppc64le';;
6515faa1
JJ
133 *) return 1;;
134 esac
135}
2a9a0a08 136
6515faa1
JJ
137run_exclusively() {
138 local lock_name="$1"
139 local timeout=$2
140 shift 2
2a9a0a08 141
6515faa1 142 mkdir -p "$LOCAL_STATE_DIR/lock/subsys"
2a9a0a08 143
6515faa1
JJ
144 local retval
145 {
04fa4e12
JJ
146 echo -n "Obtaining an exclusive lock..."
147 if ! flock -x 9; then
6515faa1
JJ
148 echo ' failed.'
149 return 1
150 fi
151 echo ' done'
152
153 "$@"; retval=$?
154 } 9> "$LOCAL_STATE_DIR/lock/subsys/lxc-alpine-$lock_name"
155
156 return $retval
2a9a0a08
NC
157}
158
6515faa1
JJ
159
160#============================ Bootstrap ===========================#
161
162bootstrap() {
5845ac2b 163 if [ "$FLUSH_CACHE" = 'yes' ] && [ -d "$LXC_CACHE_DIR/bootstrap" ]; then
6515faa1
JJ
164 einfo 'Cleaning cached bootstrap files'
165 rm -Rf "$LXC_CACHE_DIR/bootstrap"
166 fi
167
168 einfo 'Fetching and/or verifying APK keys'
169 fetch_apk_keys "$APK_KEYS_DIR"
170
171 if [ ! -x "$APK" ]; then
172 einfo 'Fetching apk-tools static binary'
173
174 local host_arch=$(parse_arch $(uname -m))
175 fetch_apk_static "$LXC_CACHE_DIR/bootstrap" "$host_arch"
176 fi
2a9a0a08
NC
177}
178
6515faa1
JJ
179fetch_apk_keys() {
180 local dest="$1"
181 local line keyname
182
183 mkdir -p "$dest"
04fa4e12 184 cd "$dest"
6515faa1
JJ
185
186 echo "$APK_KEYS_SHA256" | while read -r line; do
187 keyname="${line##* }"
fc20af63 188 if [ ! -s "$keyname" ]; then
04fa4e12 189 fetch "$APK_KEYS_URI/$keyname" > "$keyname"
6515faa1 190 fi
04fa4e12 191 echo "$line" | sha256sum -c -
6515faa1
JJ
192 done || exit 2
193
5845ac2b 194 cd - >/dev/null
6515faa1
JJ
195}
196
197fetch_apk_static() {
198 local dest="$1"
199 local arch="$2"
200 local pkg_name='apk-tools-static'
201
202 mkdir -p "$dest"
203
04fa4e12
JJ
204 local pkg_ver=$(fetch "$MIRROR_URL/latest-stable/main/$arch/APKINDEX.tar.gz" \
205 | tar -xzO APKINDEX \
6515faa1
JJ
206 | sed -n "/P:${pkg_name}/,/^$/ s/V:\(.*\)$/\1/p")
207
208 [ -n "$pkg_ver" ] || die 2 "Cannot find a version of $pkg_name in APKINDEX"
209
04fa4e12
JJ
210 fetch "$MIRROR_URL/latest-stable/main/$arch/${pkg_name}-${pkg_ver}.apk" \
211 | tar -xz -C "$dest" sbin/ # --extract --gzip --directory
6515faa1 212
fc20af63 213 [ -s "$dest/sbin/apk.static" ] || die 2 'apk.static not found'
6515faa1
JJ
214
215 local keyname=$(echo "$dest"/sbin/apk.static.*.pub | sed 's/.*\.SIGN\.RSA\.//')
216 openssl dgst -sha1 \
217 -verify "$APK_KEYS_DIR/$keyname" \
218 -signature "$dest/sbin/apk.static.SIGN.RSA.$keyname" \
219 "$dest/sbin/apk.static" \
220 || die 2 'Signature verification for apk.static failed'
221
222 # Note: apk doesn't return 0 for --version
223 local out="$("$dest"/sbin/apk.static --version)"
224 echo "$out"
225
04fa4e12 226 [ "${out%% *}" = 'apk-tools' ] || die 3 'apk.static --version failed'
6515faa1
JJ
227}
228
229
230#============================ Install ============================#
231
232install() {
6515faa1
JJ
233 local dest="$1"
234 local arch="$2"
235 local branch="$3"
1125e053 236 local extra_packages="$4"
20f39db7 237 local apk_cache="$LXC_CACHE_DIR/apk/$arch"
6515faa1 238
20f39db7
JJ
239 if [ "$FLUSH_CACHE" = 'yes' ] && [ -d "$apk_cache" ]; then
240 einfo "Cleaning cached APK packages for $arch"
241 rm -Rf "$apk_cache"
242 fi
243 mkdir -p "$apk_cache"
244
245 einfo "Installing Alpine Linux in $dest"
246 cd "$dest"
6515faa1 247
20f39db7
JJ
248 mkdir -p etc/apk
249 ln -s "$apk_cache" etc/apk/cache
28814221
JJ
250
251 local repo; for repo in main community; do
252 echo "$MIRROR_URL/$branch/$repo" >> etc/apk/repositories
253 done
6515faa1 254
691202ae 255 install_packages "$arch" "alpine-base $extra_packages"
6515faa1
JJ
256 make_dev_nodes
257 setup_inittab
258 setup_hosts
259 setup_network
260 setup_services
261
262 chroot . /bin/true \
263 || die 3 'Failed to execute /bin/true in chroot, the builded rootfs is broken!'
264
20f39db7 265 rm etc/apk/cache
5845ac2b 266 cd - >/dev/null
6515faa1
JJ
267}
268
1125e053 269install_packages() {
7276799b
VC
270 local arch="$1"
271 local packages="$2"
1125e053
JJ
272
273 $APK --arch="$arch" --root=. --keys-dir="$APK_KEYS_DIR" \
274 --update-cache --initdb add $packages
275}
276
6515faa1
JJ
277make_dev_nodes() {
278 mkdir -p -m 755 dev/pts
279 mkdir -p -m 1777 dev/shm
280
281 mknod -m 666 dev/zero c 1 5
282 mknod -m 666 dev/full c 1 7
283 mknod -m 666 dev/random c 1 8
284 mknod -m 666 dev/urandom c 1 9
285
286 local i; for i in $(seq 0 4); do
287 mknod -m 620 dev/tty$i c 4 $i
288 chown 0:5 dev/tty$i # root:tty
289 done
290
291 mknod -m 666 dev/tty c 5 0
292 chown 0:5 dev/tty # root:tty
293 mknod -m 620 dev/console c 5 1
294 mknod -m 666 dev/ptmx c 5 2
295 chown 0:5 dev/ptmx # root:tty
296}
297
298setup_inittab() {
299 # Remove unwanted ttys.
300 sed -i '/^tty[5-9]\:\:.*$/d' etc/inittab
301
302 cat <<-EOF >> etc/inittab
303 # Main LXC console console
304 ::respawn:/sbin/getty 38400 console
305 EOF
306}
307
308setup_hosts() {
309 # This runscript injects localhost entries with the current hostname
310 # into /etc/hosts.
311 cat <<'EOF' > etc/init.d/hosts
312#!/sbin/openrc-run
313
314start() {
315 local start_tag='# begin generated'
316 local end_tag='# end generated'
317
318 local content=$(
319 cat <<-EOF
320 $start_tag by /etc/init.d/hosts
321 127.0.0.1 $(hostname).local $(hostname) localhost
322 ::1 $(hostname).local $(hostname) localhost
323 $end_tag
324 EOF
325 )
326
327 if grep -q "^${start_tag}" /etc/hosts; then
328 # escape \n, busybox sed doesn't like them
329 content=${content//$'\n'/\\$'\n'}
330
331 sed -ni "/^${start_tag}/ {
332 a\\${content}
333 # read and discard next line and repeat until $end_tag or EOF
334 :a; n; /^${end_tag}/!ba; n
335 }; p" /etc/hosts
336 else
5845ac2b 337 printf "$content" >> /etc/hosts
6515faa1
JJ
338 fi
339}
e5846a6f 340EOF
6515faa1
JJ
341 chmod +x etc/init.d/hosts
342
343 # Wipe it, will be generated by the above runscript.
344 echo -n > etc/hosts
2a9a0a08
NC
345}
346
6515faa1
JJ
347setup_network() {
348 # Note: loopback is automatically started by LXC.
349 cat <<-EOF > etc/network/interfaces
350 auto eth0
351 iface eth0 inet dhcp
1c4ea80c 352 hostname \$(hostname)
6515faa1 353 EOF
2a9a0a08
NC
354}
355
6515faa1
JJ
356setup_services() {
357 local svc_name
358
359 # Specify the LXC subsystem.
360 sed -i 's/^#*rc_sys=.*/rc_sys="lxc"/' etc/rc.conf
361
362 # boot runlevel
363 for svc_name in bootmisc hosts syslog; do
364 ln -s /etc/init.d/$svc_name etc/runlevels/boot/$svc_name
365 done
2a9a0a08 366
6515faa1 367 # default runlevel
b68d0b8c
AA
368 for svc_name in networking cron crond; do
369 # issue 1164: alpine renamed cron to crond
370 # Use the one that exists.
371 if [ -e etc/init.d/$svc_name ]; then
372 ln -s /etc/init.d/$svc_name etc/runlevels/default/$svc_name
373 fi
6515faa1
JJ
374 done
375}
376
377
378#=========================== Configure ===========================#
379
380configure_container() {
381 local config="$1"
382 local hostname="$2"
383 local arch="$3"
384
385 cat <<-EOF >> "$config"
386
387 # Specify container architecture.
388 lxc.arch = $arch
389
390 # Set hostname.
b67771bc 391 lxc.uts.name = $hostname
6515faa1
JJ
392
393 # If something doesn't work, try to comment this out.
394 # Dropping sys_admin disables container root from doing a lot of things
395 # that could be bad like re-mounting lxc fstab entries rw for example,
396 # but also disables some useful things like being able to nfs mount, and
397 # things that are already namespaced with ns_capable() kernel checks, like
398 # hostname(1).
399 lxc.cap.drop = sys_admin
400
401 # Include common configuration.
402 lxc.include = $LXC_TEMPLATE_CONFIG/alpine.common.conf
403 EOF
404}
405
406
407#============================= Main ==============================#
408
6515faa1
JJ
409if [ "$(id -u)" != "0" ]; then
410 die 1 "This script must be run as 'root'"
5be56973
SH
411fi
412
6515faa1
JJ
413# Parse command options.
414options=$(getopt -o a:dFm:n:p:r:h -l arch:,debug,flush-cache,mirror:,name:,\
04fa4e12 415path:,release:,rootfs:,help,mapped-uid:,mapped-gid: -- "$@")
5afb8096
KR
416eval set -- "$options"
417
6515faa1
JJ
418# Clean variables and set defaults.
419arch="$(uname -m)"
420debug='no'
421flush_cache='no'
422mirror_url=
423name=
424path=
425release=
426rootfs=
427
428# Process command options.
2a9a0a08 429while [ $# -gt 0 ]; do
6515faa1
JJ
430 case $1 in
431 -a | --arch)
432 arch=$2; shift 2
433 ;;
434 -d | --debug)
435 debug='yes'; shift 1
436 ;;
437 -F | --flush-cache)
438 flush_cache='yes'; shift 1
439 ;;
440 -m | --mirror)
441 mirror_url=$2; shift 2
442 ;;
443 -n | --name)
444 name=$2; shift 2
445 ;;
446 -p | --path)
447 path=$2; shift 2
448 ;;
449 -r | --release)
450 release=$2; shift 2
451 ;;
452 --rootfs)
453 rootfs=$2; shift 2
454 ;;
455 -h | --help)
456 usage; exit 0
457 ;;
458 --)
459 shift; break
460 ;;
04fa4e12 461 --mapped-[ug]id)
5845ac2b
JJ
462 die 1 "This template can't be used for unprivileged containers." \
463 'You may want to try the "download" template instead.'
04fa4e12 464 ;;
6515faa1 465 *)
5845ac2b 466 echo "Unknown option: $1" 1>&2
6515faa1
JJ
467 usage; exit 1
468 ;;
469 esac
2a9a0a08
NC
470done
471
1125e053
JJ
472extra_packages="$@"
473
6515faa1 474[ "$debug" = 'yes' ] && set -x
2a9a0a08 475
6515faa1
JJ
476# Set global variables.
477readonly DEBUG="$debug"
478readonly FLUSH_CACHE="$flush_cache"
72ead1c0 479readonly MIRROR_URL="${mirror_url:-$DEFAULT_MIRROR_URL}"
2a9a0a08 480
6515faa1
JJ
481# Validate options.
482[ -n "$name" ] || die 1 'Missing required option --name'
483[ -n "$path" ] || die 1 'Missing required option --path'
2a9a0a08 484
5845ac2b 485if [ -z "$rootfs" ] && [ -f "$path/config" ]; then
7a96a068 486 rootfs="$(sed -nE 's/^lxc.rootfs.path\s*=\s*(.*)$/\1/p' "$path/config")"
6515faa1 487fi
2a9a0a08 488if [ -z "$rootfs" ]; then
6515faa1 489 rootfs="$path/rootfs"
2a9a0a08
NC
490fi
491
6515faa1
JJ
492arch=$(parse_arch "$arch") \
493 || die 1 "Unsupported architecture: $arch"
494
495if [ -z "$release" ]; then
496 release=$(latest_release_branch "$arch") \
497 || die 2 'Failed to resolve Alpine last release branch'
569bee5c
NC
498fi
499
6515faa1
JJ
500# Here we go!
501run_exclusively 'bootstrap' 10 bootstrap
1125e053 502run_exclusively "$arch" 30 install "$rootfs" "$arch" "$release" "$extra_packages"
6515faa1
JJ
503configure_container "$path/config" "$name" "$arch"
504
505einfo "Container's rootfs and config have been created"
506cat <<-EOF
507 Edit the config file $path/config to check/enable networking setup.
508 The installed system is preconfigured for a loopback and single network
509 interface configured via DHCP.
510
511 To start the container, run "lxc-start -n $name".
512 The root password is not set; to enter the container run "lxc-attach -n $name".
513EOF