1
0
mirror of https://github.com/lxc/lxc-templates.git synced 2024-12-22 14:30:31 +00:00
lxc-templates/templates/lxc-alpine.in
4oo4 2163a7e4e0
Update alpine keys URI - fix package verification
Alpine started using https://git.alpinelinux.org/aports/plain/main/alpine-keys/ to publish their apk keys as of 3.15, so using the old URI will give errors about signature verification since a required key is missing

Remove old key

Fix #43 - let lxc config create character devices

Creating lxc containers will error out when trying to create character devices with "file exists". This does not appear to be necessary for most of them since they get created when you actually start the container.

Signed-off-by: 4oo4 <4oo4@users.noreply.github.com>
2022-06-08 04:25:14 +00:00

524 lines
14 KiB
Bash

#!/bin/sh
# vim: set ts=4:
# Exit on error and treat unset variables as an error.
set -eu
#
# LXC template for Alpine Linux 3+
#
# Note: Do not replace tabs with spaces, it would break heredocs!
# Authors:
# Jakub Jirutka <jakub@jirutka.cz>
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
#=========================== Constants ============================#
# Make sure the usual locations are in PATH
export PATH=$PATH:/usr/sbin:/usr/bin:/sbin:/bin
readonly LOCAL_STATE_DIR='@LOCALSTATEDIR@'
readonly LXC_TEMPLATE_CONFIG='@LXCTEMPLATECONFIG@'
readonly LXC_CACHE_DIR="${LXC_CACHE_PATH:-"$LOCAL_STATE_DIR/cache/lxc"}/alpine"
# SHA256 checksums of GPG keys for APK.
readonly APK_KEYS_SHA256="\
9c102bcc376af1498d549b77bdbfa815ae86faa1d2d82f040e616b18ef2df2d4 alpine-devel@lists.alpinelinux.org-4a6a0840.rsa.pub
ebf31683b56410ecc4c00acd9f6e2839e237a3b62b5ae7ef686705c7ba0396a9 alpine-devel@lists.alpinelinux.org-5243ef4b.rsa.pub
1bb2a846c0ea4ca9d0e7862f970863857fc33c32f5506098c636a62a726a847b alpine-devel@lists.alpinelinux.org-524d27bb.rsa.pub
12f899e55a7691225603d6fb3324940fc51cd7f133e7ead788663c2b7eecb00c alpine-devel@lists.alpinelinux.org-5261cecb.rsa.pub
73867d92083f2f8ab899a26ccda7ef63dfaa0032a938620eda605558958a8041 alpine-devel@lists.alpinelinux.org-58199dcc.rsa.pub
9a4cd858d9710963848e6d5f555325dc199d1c952b01cf6e64da2c15deedbd97 alpine-devel@lists.alpinelinux.org-58cbb476.rsa.pub
780b3ed41786772cbc7b68136546fa3f897f28a23b30c72dde6225319c44cfff alpine-devel@lists.alpinelinux.org-58e4f17d.rsa.pub
59c01c57b446633249f67c04b115dd6787f4378f183dff2bbf65406df93f176d alpine-devel@lists.alpinelinux.org-5e69ca50.rsa.pub
db0b49163f07ffba64a5ca198bcf1688610b0bd1f0d8d5afeaf78559d73f2278 alpine-devel@lists.alpinelinux.org-60ac2099.rsa.pub
207e4696d3c05f7cb05966aee557307151f1f00217af4143c1bcaf33b8df733f alpine-devel@lists.alpinelinux.org-6165ee59.rsa.pub
128d34d4aec39b0daedea8163cd8dc24dff36fd3d848630ab97eeb1d3084bbb3 alpine-devel@lists.alpinelinux.org-61666e3f.rsa.pub
10877cce0a935e46ad88cb79e174a2491680508eccda08e92bf04fb9bf37fbc1 alpine-devel@lists.alpinelinux.org-616a9724.rsa.pub
4a095a9daca86da496a3cd9adcd95ee2197fdbeb84638656d469f05a4d740751 alpine-devel@lists.alpinelinux.org-616abc23.rsa.pub
0caf5662fde45616d88cfd7021b7bda269a2fcaf311e51c48945a967a609ec0b alpine-devel@lists.alpinelinux.org-616ac3bc.rsa.pub
ebe717d228555aa58133c202314a451f81e71f174781fd7ff8d8970d6cfa60da alpine-devel@lists.alpinelinux.org-616adfeb.rsa.pub
d11f6b21c61b4274e182eb888883a8ba8acdbf820dcc7a6d82a7d9fc2fd2836d alpine-devel@lists.alpinelinux.org-616ae350.rsa.pub
40a216cbd163f22e5f16a9e0929de7cde221b9cbae8e36aa368b1e128afe0a31 alpine-devel@lists.alpinelinux.org-616db30d.rsa.pub"
readonly APK_KEYS_URI='https://git.alpinelinux.org/aports/plain/main/alpine-keys/'
readonly DEFAULT_MIRROR_URL='http://dl-cdn.alpinelinux.org/alpine'
: ${APK_KEYS_DIR:=/etc/apk/keys}
if ! ls "$APK_KEYS_DIR"/alpine* >/dev/null 2>&1; then
APK_KEYS_DIR="$LXC_CACHE_DIR/bootstrap/keys"
fi
readonly APK_KEYS_DIR
: ${APK:=$(command -v apk || true)}
if [ ! -x "$APK" ]; then
APK="$LXC_CACHE_DIR/bootstrap/sbin/apk.static"
fi
readonly APK
#======================== Helper Functions ========================#
usage() {
cat <<-EOF
Template specific options can be passed to lxc-create after a '--' like this:
lxc-create --name=NAME [lxc-create-options] -- [template-options] [PKG...]
PKG Additional APK package(s) to install into the container.
Template options:
-a ARCH, --arch=ARCH The container architecture (e.g. x86, x86_64); defaults
to the host arch.
-d, --debug Run this script in a debug mode (set -x and wget w/o -q).
-F, --flush-cache Remove cached files before build.
-m URL --mirror=URL The Alpine mirror to use; defaults to $DEFAULT_MIRROR_URL.
-r VER, --release=VER The Alpine release branch to install; default is the
latest stable.
Environment variables:
APK The apk-tools binary to use when building rootfs. If not set
or not executable and apk is not on PATH, then the script
will download the latest apk-tools-static.
APK_KEYS_DIR Path to directory with GPG keys for APK. If not set and
/etc/apk/keys does not contain alpine keys, then the script
will download the keys from ${APK_KEYS_URI}.
LXC_CACHE_PATH Path to the cache directory where to store bootstrap files
and APK packages.
EOF
}
die() {
local retval=$1; shift
printf 'ERROR: %s\n' "$@" 1>&2
exit $retval
}
einfo() {
printf "\n==> $1\n"
}
fetch() {
if [ "$DEBUG" = 'yes' ]; then
wget -T 10 -O - $@
else
wget -T 10 -O - -q $@
fi
}
latest_release_branch() {
local arch="$1"
local branch=$(fetch "$MIRROR_URL/latest-stable/releases/$arch/latest-releases.yaml" \
| sed -En 's/^[ \t]*branch: (.*)$/\1/p' \
| head -n 1)
[ -n "$branch" ] && echo "$branch"
}
parse_arch() {
case "$1" in
x86 | i[3-6]86) echo 'x86';;
x86_64 | amd64) echo 'x86_64';;
aarch64 | arm64) echo 'aarch64';;
armv7) echo 'armv7';;
arm*) echo 'armhf';;
ppc64le) echo 'ppc64le';;
*) return 1;;
esac
}
run_exclusively() {
local lock_name="$1"
local timeout=$2
shift 2
mkdir -p "$LOCAL_STATE_DIR/lock/subsys"
local retval
{
echo -n "Obtaining an exclusive lock..."
if ! flock -x 9; then
echo ' failed.'
return 1
fi
echo ' done'
"$@"; retval=$?
} 9> "$LOCAL_STATE_DIR/lock/subsys/lxc-alpine-$lock_name"
return $retval
}
#============================ Bootstrap ===========================#
bootstrap() {
if [ "$FLUSH_CACHE" = 'yes' ] && [ -d "$LXC_CACHE_DIR/bootstrap" ]; then
einfo 'Cleaning cached bootstrap files'
rm -Rf "$LXC_CACHE_DIR/bootstrap"
fi
einfo 'Fetching and/or verifying APK keys'
fetch_apk_keys "$APK_KEYS_DIR"
if [ ! -x "$APK" ]; then
einfo 'Fetching apk-tools static binary'
local host_arch=$(parse_arch $(uname -m))
fetch_apk_static "$LXC_CACHE_DIR/bootstrap" "$host_arch"
fi
}
fetch_apk_keys() {
local dest="$1"
local line keyname
mkdir -p "$dest"
cd "$dest"
echo "$APK_KEYS_SHA256" | while read -r line; do
keyname="${line##* }"
if [ ! -s "$keyname" ]; then
fetch "$APK_KEYS_URI/$keyname" > "$keyname"
fi
echo "$line" | sha256sum -c -
done || exit 2
cd - >/dev/null
}
fetch_apk_static() {
local dest="$1"
local arch="$2"
local pkg_name='apk-tools-static'
mkdir -p "$dest"
local pkg_ver=$(fetch "$MIRROR_URL/latest-stable/main/$arch/APKINDEX.tar.gz" \
| tar -xzO APKINDEX \
| sed -n "/P:${pkg_name}/,/^$/ s/V:\(.*\)$/\1/p")
[ -n "$pkg_ver" ] || die 2 "Cannot find a version of $pkg_name in APKINDEX"
fetch "$MIRROR_URL/latest-stable/main/$arch/${pkg_name}-${pkg_ver}.apk" \
| tar -xz -C "$dest" sbin/ # --extract --gzip --directory
[ -s "$dest/sbin/apk.static" ] || die 2 'apk.static not found'
local keyname=$(echo "$dest"/sbin/apk.static.*.pub | sed 's/.*\.SIGN\.RSA\.//')
openssl dgst -sha1 \
-verify "$APK_KEYS_DIR/$keyname" \
-signature "$dest/sbin/apk.static.SIGN.RSA.$keyname" \
"$dest/sbin/apk.static" \
|| die 2 'Signature verification for apk.static failed'
# Note: apk doesn't return 0 for --version
local out="$("$dest"/sbin/apk.static --version)"
echo "$out"
[ "${out%% *}" = 'apk-tools' ] || die 3 'apk.static --version failed'
}
#============================ Install ============================#
install() {
local dest="$1"
local arch="$2"
local branch="$3"
local extra_packages="$4"
local apk_cache="$LXC_CACHE_DIR/apk/$arch"
if [ "$FLUSH_CACHE" = 'yes' ] && [ -d "$apk_cache" ]; then
einfo "Cleaning cached APK packages for $arch"
rm -Rf "$apk_cache"
fi
mkdir -p "$apk_cache"
einfo "Installing Alpine Linux in $dest"
cd "$dest"
mkdir -p etc/apk
ln -s "$apk_cache" etc/apk/cache
local repo; for repo in main community; do
echo "$MIRROR_URL/$branch/$repo" >> etc/apk/repositories
done
install_packages "$arch" "alpine-base $extra_packages"
make_dev_nodes
setup_inittab
setup_hosts
setup_network
setup_services
chroot . /bin/true \
|| die 3 'Failed to execute /bin/true in chroot, the builded rootfs is broken!'
rm etc/apk/cache
cd - >/dev/null
}
install_packages() {
local arch="$1"
local packages="$2"
$APK --arch="$arch" --root=. --keys-dir="$APK_KEYS_DIR" \
--update-cache --initdb add $packages
}
make_dev_nodes() {
mkdir -p -m 755 dev/pts
mkdir -p -m 1777 dev/shm
local i; for i in $(seq 0 4); do
mknod -m 620 dev/tty$i c 4 $i
chown 0:5 dev/tty$i # root:tty
done
mknod -m 666 dev/tty c 5 0
chown 0:5 dev/tty # root:tty
mknod -m 620 dev/console c 5 1
mknod -m 666 dev/ptmx c 5 2
chown 0:5 dev/ptmx # root:tty
}
setup_inittab() {
# Remove unwanted ttys.
sed -i '/^tty[5-9]\:\:.*$/d' etc/inittab
cat <<-EOF >> etc/inittab
# Main LXC console console
::respawn:/sbin/getty 38400 console
EOF
}
setup_hosts() {
# This runscript injects localhost entries with the current hostname
# into /etc/hosts.
cat <<'EOF' > etc/init.d/hosts
#!/sbin/openrc-run
start() {
local start_tag='# begin generated'
local end_tag='# end generated'
local content=$(
cat <<-EOF
$start_tag by /etc/init.d/hosts
127.0.0.1 $(hostname).local $(hostname) localhost
::1 $(hostname).local $(hostname) localhost
$end_tag
EOF
)
if grep -q "^${start_tag}" /etc/hosts; then
# escape \n, busybox sed doesn't like them
content=${content//$'\n'/\\$'\n'}
sed -ni "/^${start_tag}/ {
a\\${content}
# read and discard next line and repeat until $end_tag or EOF
:a; n; /^${end_tag}/!ba; n
}; p" /etc/hosts
else
printf "$content" >> /etc/hosts
fi
}
EOF
chmod +x etc/init.d/hosts
# Wipe it, will be generated by the above runscript.
echo -n > etc/hosts
}
setup_network() {
# Note: loopback is automatically started by LXC.
cat <<-EOF > etc/network/interfaces
auto eth0
iface eth0 inet dhcp
hostname \$(hostname)
EOF
}
setup_services() {
local svc_name
# Specify the LXC subsystem.
sed -i 's/^#*rc_sys=.*/rc_sys="lxc"/' etc/rc.conf
# boot runlevel
for svc_name in bootmisc hosts syslog; do
ln -s /etc/init.d/$svc_name etc/runlevels/boot/$svc_name
done
# default runlevel
for svc_name in networking cron crond; do
# issue 1164: alpine renamed cron to crond
# Use the one that exists.
if [ -e etc/init.d/$svc_name ]; then
ln -s /etc/init.d/$svc_name etc/runlevels/default/$svc_name
fi
done
}
#=========================== Configure ===========================#
configure_container() {
local config="$1"
local hostname="$2"
local arch="$3"
cat <<-EOF >> "$config"
# Specify container architecture.
lxc.arch = $arch
# Set hostname.
lxc.uts.name = $hostname
# If something doesn't work, try to comment this out.
# Dropping sys_admin disables container root from doing a lot of things
# that could be bad like re-mounting lxc fstab entries rw for example,
# but also disables some useful things like being able to nfs mount, and
# things that are already namespaced with ns_capable() kernel checks, like
# hostname(1).
lxc.cap.drop = sys_admin
# Comment this out if you have to debug processes by tracing.
lxc.cap.drop = sys_ptrace
# Comment this out if required by your applications.
lxc.cap.drop = setpcap
# Include common configuration.
lxc.include = $LXC_TEMPLATE_CONFIG/alpine.common.conf
EOF
}
#============================= Main ==============================#
if [ "$(id -u)" != "0" ]; then
die 1 "This script must be run as 'root'"
fi
# Parse command options.
options=$(getopt -o a:dFm:n:p:r:h -l arch:,debug,flush-cache,mirror:,name:,\
path:,release:,rootfs:,help,mapped-uid:,mapped-gid: -- "$@")
eval set -- "$options"
# Clean variables and set defaults.
arch="$(uname -m)"
debug='no'
flush_cache='no'
mirror_url=
name=
path=
release=
rootfs=
# Process command options.
while [ $# -gt 0 ]; do
case $1 in
-a | --arch)
arch=$2; shift 2
;;
-d | --debug)
debug='yes'; shift 1
;;
-F | --flush-cache)
flush_cache='yes'; shift 1
;;
-m | --mirror)
mirror_url=$2; shift 2
;;
-n | --name)
name=$2; shift 2
;;
-p | --path)
path=$2; shift 2
;;
-r | --release)
release=$2; shift 2
;;
--rootfs)
rootfs=$2; shift 2
;;
-h | --help)
usage; exit 0
;;
--)
shift; break
;;
--mapped-[ug]id)
die 1 "This template can't be used for unprivileged containers." \
'You may want to try the "download" template instead.'
;;
*)
echo "Unknown option: $1" 1>&2
usage; exit 1
;;
esac
done
extra_packages="$@"
[ "$debug" = 'yes' ] && set -x
# Set global variables.
readonly DEBUG="$debug"
readonly FLUSH_CACHE="$flush_cache"
readonly MIRROR_URL="${mirror_url:-$DEFAULT_MIRROR_URL}"
# Validate options.
[ -n "$name" ] || die 1 'Missing required option --name'
[ -n "$path" ] || die 1 'Missing required option --path'
if [ -z "$rootfs" ] && [ -f "$path/config" ]; then
rootfs="$(sed -nE 's/^lxc.rootfs.path\s*=\s*(.*)$/\1/p' "$path/config")"
fi
if [ -z "$rootfs" ]; then
rootfs="$path/rootfs"
fi
arch=$(parse_arch "$arch") \
|| die 1 "Unsupported architecture: $arch"
if [ -z "$release" ]; then
release=$(latest_release_branch "$arch") \
|| die 2 'Failed to resolve Alpine last release branch'
fi
# Here we go!
run_exclusively 'bootstrap' 10 bootstrap
run_exclusively "$arch" 30 install "$rootfs" "$arch" "$release" "$extra_packages"
configure_container "$path/config" "$name" "$arch"
einfo "Container's rootfs and config have been created"
cat <<-EOF
Edit the config file $path/config to check/enable networking setup.
The installed system is preconfigured for a loopback and single network
interface configured via DHCP.
To start the container, run "lxc-start -n $name".
The root password is not set; to enter the container run "lxc-attach -n $name".
EOF