mirror of
https://git.proxmox.com/git/mirror_lxc
synced 2025-07-24 15:42:24 +00:00
329 lines
8.2 KiB
Bash
329 lines
8.2 KiB
Bash
#!/bin/sh
|
|
|
|
# Client script for LXC container images.
|
|
#
|
|
# Copyright @ Daniel Lezcano <daniel.lezcano@free.fr>
|
|
# Copyright © 2018 Christian Brauner <christian.brauner@ubuntu.com>
|
|
#
|
|
# 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
|
|
|
|
LXC_MAPPED_UID=
|
|
LXC_MAPPED_GID=
|
|
|
|
# Make sure the usual locations are in PATH
|
|
export PATH=$PATH:/usr/sbin:/usr/bin:/sbin:/bin
|
|
|
|
in_userns() {
|
|
[ -e /proc/self/uid_map ] || { echo no; return; }
|
|
while read -r line; do
|
|
fields="$(echo "$line" | awk '{ print $1 " " $2 " " $3 }')"
|
|
if [ "${fields}" = "0 0 4294967295" ]; then
|
|
echo no;
|
|
return;
|
|
fi
|
|
if echo "${fields}" | grep -q " 0 1$"; then
|
|
echo userns-root;
|
|
return;
|
|
fi
|
|
done < /proc/self/uid_map
|
|
|
|
[ "$(cat /proc/self/uid_map)" = "$(cat /proc/1/uid_map)" ] && { echo userns-root; return; }
|
|
echo yes
|
|
}
|
|
|
|
USERNS="$(in_userns)"
|
|
|
|
install_busybox()
|
|
{
|
|
rootfs="${1}"
|
|
name="${2}"
|
|
res=0
|
|
fstree="\
|
|
${rootfs}/selinux \
|
|
${rootfs}/dev \
|
|
${rootfs}/home \
|
|
${rootfs}/root \
|
|
${rootfs}/etc \
|
|
${rootfs}/etc/init.d \
|
|
${rootfs}/bin \
|
|
${rootfs}/usr/bin \
|
|
${rootfs}/sbin \
|
|
${rootfs}/usr/sbin \
|
|
${rootfs}/proc \
|
|
${rootfs}/sys \
|
|
${rootfs}/mnt \
|
|
${rootfs}/tmp \
|
|
${rootfs}/var/log \
|
|
${rootfs}/usr/share/udhcpc \
|
|
${rootfs}/dev/pts \
|
|
${rootfs}/dev/shm \
|
|
${rootfs}/lib \
|
|
${rootfs}/usr/lib \
|
|
${rootfs}/lib64 \
|
|
${rootfs}/usr/lib64"
|
|
|
|
# shellcheck disable=SC2086
|
|
mkdir -p ${fstree} || return 1
|
|
# shellcheck disable=SC2086
|
|
chmod 755 ${fstree} || return 1
|
|
|
|
# minimal devices needed for busybox
|
|
if [ "${USERNS}" = "yes" ]; then
|
|
for dev in tty console tty0 tty1 ram0 null urandom; do
|
|
echo "lxc.mount.entry = /dev/${dev} dev/${dev} none bind,optional,create=file 0 0" >> "${path}/config"
|
|
done
|
|
else
|
|
mknod -m 666 "${rootfs}/tty" c 5 0 || res=1
|
|
mknod -m 666 "${rootfs}/console" c 5 1 || res=1
|
|
mknod -m 666 "${rootfs}/tty0" c 4 0 || res=1
|
|
mknod -m 666 "${rootfs}/tty1" c 4 0 || res=1
|
|
mknod -m 666 "${rootfs}/tty5" c 4 0 || res=1
|
|
mknod -m 600 "${rootfs}/ram0" b 1 0 || res=1
|
|
mknod -m 666 "${rootfs}/null" c 1 3 || res=1
|
|
mknod -m 666 "${rootfs}/zero" c 1 5 || res=1
|
|
mknod -m 666 "${rootfs}/urandom" c 1 9 || res=1
|
|
fi
|
|
|
|
# root user defined
|
|
cat <<EOF >> "${rootfs}/etc/passwd"
|
|
root:x:0:0:root:/root:/bin/sh
|
|
EOF
|
|
|
|
cat <<EOF >> "${rootfs}/etc/group"
|
|
root:x:0:root
|
|
EOF
|
|
|
|
# mount everything
|
|
cat <<EOF >> "${rootfs}/etc/init.d/rcS"
|
|
#!/bin/sh
|
|
/bin/syslogd
|
|
/bin/mount -a
|
|
/bin/udhcpc
|
|
EOF
|
|
|
|
# executable
|
|
chmod 744 "${rootfs}/etc/init.d/rcS" || return 1
|
|
|
|
# launch rcS first then make a console available
|
|
# and propose a shell on the tty, the last one is
|
|
# not needed
|
|
cat <<EOF >> "${rootfs}/etc/inittab"
|
|
::sysinit:/etc/init.d/rcS
|
|
tty1::respawn:/bin/getty -L tty1 115200 vt100
|
|
console::askfirst:/bin/sh
|
|
EOF
|
|
# writable and readable for other
|
|
chmod 644 "${rootfs}/etc/inittab" || return 1
|
|
|
|
cat <<EOF >> "${rootfs}/usr/share/udhcpc/default.script"
|
|
#!/bin/sh
|
|
case "\$1" in
|
|
deconfig)
|
|
ip addr flush dev \$interface
|
|
;;
|
|
|
|
renew|bound)
|
|
# flush all the routes
|
|
if [ -n "\$router" ]; then
|
|
ip route del default 2> /dev/null
|
|
fi
|
|
|
|
# check broadcast
|
|
if [ -n "\$broadcast" ]; then
|
|
broadcast="broadcast \$broadcast"
|
|
fi
|
|
|
|
# add a new ip address
|
|
ip addr add \$ip/\$mask \$broadcast dev \$interface
|
|
|
|
if [ -n "\$router" ]; then
|
|
ip route add default via \$router dev \$interface
|
|
fi
|
|
|
|
[ -n "\$domain" ] && echo search \$domain > /etc/resolv.conf
|
|
for i in \$dns ; do
|
|
echo nameserver \$i >> /etc/resolv.conf
|
|
done
|
|
;;
|
|
esac
|
|
exit 0
|
|
EOF
|
|
|
|
chmod 744 "${rootfs}/usr/share/udhcpc/default.script"
|
|
|
|
return "${res}"
|
|
}
|
|
|
|
configure_busybox()
|
|
{
|
|
rootfs="${1}"
|
|
|
|
if ! which busybox > /dev/null 2>&1; then
|
|
echo "ERROR: Failed to find busybox binary"
|
|
return 1
|
|
fi
|
|
|
|
# copy busybox in the rootfs
|
|
if ! cp "$(which busybox)" "${rootfs}/bin"; then
|
|
echo "ERROR: Failed to copy busybox binary"
|
|
return 1
|
|
fi
|
|
|
|
# symlink busybox for the commands it supports
|
|
# it would be nice to just use "chroot $rootfs busybox --install -s /bin"
|
|
# but that only works right in a chroot with busybox >= 1.19.0
|
|
(
|
|
cd "${rootfs}/bin" || return 1
|
|
./busybox --list | grep -v busybox | xargs -n1 ln -s busybox
|
|
)
|
|
|
|
# relink /sbin/init
|
|
ln "${rootfs}/bin/busybox" "${rootfs}/sbin/init"
|
|
|
|
# /etc/fstab must exist for "mount -a"
|
|
touch "${rootfs}/etc/fstab"
|
|
|
|
# passwd exec must be setuid
|
|
chmod +s "${rootfs}/bin/passwd"
|
|
touch "${rootfs}/etc/shadow"
|
|
|
|
return 0
|
|
}
|
|
|
|
copy_configuration()
|
|
{
|
|
path="${1}"
|
|
rootfs="${2}"
|
|
name="${3}"
|
|
|
|
grep -q "^lxc.rootfs.path" "${path}/config" 2>/dev/null || echo "lxc.rootfs.path = ${rootfs}" >> "${path}/config"
|
|
cat <<EOF >> "${path}/config"
|
|
lxc.signal.halt = SIGUSR1
|
|
lxc.signal.reboot = SIGTERM
|
|
lxc.uts.name = "${name}"
|
|
lxc.tty.max = 1
|
|
lxc.pty.max = 1
|
|
lxc.cap.drop = sys_module mac_admin mac_override sys_time
|
|
|
|
# When using LXC with apparmor, uncomment the next line to run unconfined:
|
|
#lxc.apparmor.profile = unconfined
|
|
|
|
lxc.mount.auto = cgroup:mixed proc:mixed sys:mixed
|
|
lxc.mount.entry = shm /dev/shm tmpfs defaults 0 0
|
|
EOF
|
|
|
|
libdirs="\
|
|
lib \
|
|
usr/lib \
|
|
lib64 \
|
|
usr/lib64"
|
|
|
|
for dir in ${libdirs}; do
|
|
if [ -d "/${dir}" ] && [ -d "${rootfs}/${dir}" ]; then
|
|
echo "lxc.mount.entry = /${dir} ${dir} none ro,bind 0 0" >> "${path}/config"
|
|
fi
|
|
done
|
|
echo "lxc.mount.entry = /sys/kernel/security sys/kernel/security none ro,bind,optional 0 0" >> "${path}/config"
|
|
}
|
|
|
|
remap_userns()
|
|
{
|
|
path="${1}"
|
|
|
|
if [ -n "$LXC_MAPPED_UID" ] && [ "$LXC_MAPPED_UID" != "-1" ]; then
|
|
chown "${LXC_MAPPED_UID}" "${path}/config" > /dev/null 2>&1
|
|
chown -R root "${path}/rootfs" > /dev/null 2>&1
|
|
fi
|
|
|
|
if [ -n "$LXC_MAPPED_GID" ] && [ "$LXC_MAPPED_GID" != "-1" ]; then
|
|
chgrp "${LXC_MAPPED_GID}" "${path}/config" > /dev/null 2>&1
|
|
chgrp -R root "${path}/rootfs" > /dev/null 2>&1
|
|
fi
|
|
}
|
|
|
|
usage() {
|
|
cat <<EOF
|
|
LXC busybox image builder
|
|
|
|
Special arguments:
|
|
[ -h | --help ]: Print this help message and exit.
|
|
|
|
LXC internal arguments (do not pass manually!):
|
|
[ --name <name> ]: The container name
|
|
[ --path <path> ]: The path to the container
|
|
[ --rootfs <rootfs> ]: The path to the container's rootfs
|
|
[ --mapped-uid <map> ]: A uid map (user namespaces)
|
|
[ --mapped-gid <map> ]: A gid map (user namespaces)
|
|
EOF
|
|
return 0
|
|
}
|
|
|
|
if ! options=$(getopt -o hp:n: -l help,rootfs:,path:,name:,mapped-uid:,mapped-gid: -- "$@"); then
|
|
usage
|
|
exit 1
|
|
fi
|
|
eval set -- "$options"
|
|
|
|
while true
|
|
do
|
|
case "$1" in
|
|
-h|--help) usage && exit 1;;
|
|
-n|--name) name=$2; shift 2;;
|
|
-p|--path) path=$2; shift 2;;
|
|
--rootfs) rootfs=$2; shift 2;;
|
|
--mapped-uid) LXC_MAPPED_UID=$2; shift 2;;
|
|
--mapped-gid) LXC_MAPPED_GID=$2; shift 2;;
|
|
--) shift 1; break ;;
|
|
*) break ;;
|
|
esac
|
|
done
|
|
|
|
# Check that we have all variables we need
|
|
if [ -z "${name}" ] || [ -z "${path}" ] || [ -z "${rootfs}" ]; then
|
|
echo "ERROR: Please pass the name, path, and rootfs for the container" 1>&2
|
|
exit 1
|
|
fi
|
|
|
|
# detect rootfs
|
|
config="$path/config"
|
|
if [ -z "$rootfs" ]; then
|
|
if grep -q '^lxc.rootfs.path' "${config}" 2> /dev/null ; then
|
|
rootfs=$(awk -F= '/^lxc.rootfs.path =/{ print $2 }' "${config}")
|
|
else
|
|
rootfs="${path}/rootfs"
|
|
fi
|
|
fi
|
|
|
|
if ! install_busybox "${rootfs}" "${name}"; then
|
|
echo "ERROR: Failed to install rootfs"
|
|
exit 1
|
|
fi
|
|
|
|
if ! configure_busybox "${rootfs}"; then
|
|
echo "ERROR: Failed to configure busybox"
|
|
exit 1
|
|
fi
|
|
|
|
if ! copy_configuration "${path}" "${rootfs}" "${name}"; then
|
|
echo "ERROR: Failed to write config file"
|
|
exit 1
|
|
fi
|
|
|
|
if ! remap_userns "${path}"; then
|
|
echo "ERROR: Failed to change idmappings"
|
|
exit 1
|
|
fi
|