summaryrefslogtreecommitdiffstats
path: root/pkg/runner/lxc-helpers-lib.sh
diff options
context:
space:
mode:
Diffstat (limited to 'pkg/runner/lxc-helpers-lib.sh')
-rwxr-xr-xpkg/runner/lxc-helpers-lib.sh434
1 files changed, 434 insertions, 0 deletions
diff --git a/pkg/runner/lxc-helpers-lib.sh b/pkg/runner/lxc-helpers-lib.sh
new file mode 100755
index 0000000..81b368b
--- /dev/null
+++ b/pkg/runner/lxc-helpers-lib.sh
@@ -0,0 +1,434 @@
+#!/bin/bash
+# SPDX-License-Identifier: MIT
+
+export DEBIAN_FRONTEND=noninteractive
+
+LXC_SELF_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
+LXC_BIN=/usr/local/bin
+LXC_CONTAINER_CONFIG_ALL="unprivileged lxc libvirt docker k8s"
+LXC_CONTAINER_CONFIG_DEFAULT="lxc libvirt docker"
+LXC_IPV6_PREFIX_DEFAULT="fc15"
+LXC_DOCKER_PREFIX_DEFAULT="172.17"
+LXC_IPV6_DOCKER_PREFIX_DEFAULT="fd00:d0ca"
+
+: ${LXC_SUDO:=}
+: ${LXC_CONTAINER_RELEASE:=bookworm}
+: ${LXC_CONTAINER_CONFIG:=$LXC_CONTAINER_CONFIG_DEFAULT}
+: ${LXC_HOME:=/home}
+: ${LXC_VERBOSE:=false}
+
+source /etc/os-release
+
+function lxc_release() {
+ echo $VERSION_CODENAME
+}
+
+function lxc_template_release() {
+ echo lxc-helpers-$LXC_CONTAINER_RELEASE
+}
+
+function lxc_root() {
+ local name="$1"
+
+ echo /var/lib/lxc/$name/rootfs
+}
+
+function lxc_config() {
+ local name="$1"
+
+ echo /var/lib/lxc/$name/config
+}
+
+function lxc_container_run() {
+ local name="$1"
+ shift
+
+ $LXC_SUDO lxc-attach --clear-env --name $name -- "$@"
+}
+
+function lxc_container_run_script_as() {
+ local name="$1"
+ local user="$2"
+ local script="$3"
+
+ $LXC_SUDO chmod +x $(lxc_root $name)$script
+ $LXC_SUDO lxc-attach --name $name -- sudo --user $user $script
+}
+
+function lxc_container_run_script() {
+ local name="$1"
+ local script="$2"
+
+ $LXC_SUDO chmod +x $(lxc_root $name)$script
+ lxc_container_run $name $script
+}
+
+function lxc_container_inside() {
+ local name="$1"
+ shift
+
+ lxc_container_run $name $LXC_BIN/lxc-helpers.sh "$@"
+}
+
+function lxc_container_user_install() {
+ local name="$1"
+ local user_id="$2"
+ local user="$3"
+
+ if test "$user" = root ; then
+ return
+ fi
+
+ local root=$(lxc_root $name)
+
+ if ! $LXC_SUDO grep --quiet "^$user " $root/etc/sudoers ; then
+ $LXC_SUDO tee $root/usr/local/bin/lxc-helpers-create-user.sh > /dev/null <<EOF
+#!/bin/bash
+set -ex
+
+mkdir -p $LXC_HOME
+useradd --base-dir $LXC_HOME --create-home --shell /bin/bash --uid $user_id $user
+for group in docker kvm libvirt ; do
+ if grep --quiet \$group /etc/group ; then adduser $user \$group ; fi
+done
+echo "$user ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers
+sudo --user $user ssh-keygen -b 2048 -N '' -f $LXC_HOME/$user/.ssh/id_rsa
+EOF
+ lxc_container_run_script $name /usr/local/bin/lxc-helpers-create-user.sh
+ fi
+}
+
+function lxc_maybe_sudo() {
+ if test $(id -u) != 0 ; then
+ LXC_SUDO=sudo
+ fi
+}
+
+function lxc_prepare_environment() {
+ lxc_maybe_sudo
+ if ! $(which lxc-create > /dev/null) ; then
+ $LXC_SUDO apt-get install -y -qq make git libvirt0 libpam-cgfs bridge-utils uidmap dnsmasq-base dnsmasq dnsmasq-utils qemu-user-static
+ fi
+}
+
+function lxc_container_config_nesting() {
+ echo 'security.nesting = true'
+}
+
+function lxc_container_config_cap() {
+ echo 'lxc.cap.drop ='
+}
+
+function lxc_container_config_net() {
+ cat <<EOF
+#
+# /dev/net
+#
+lxc.cgroup2.devices.allow = c 10:200 rwm
+lxc.mount.entry = /dev/net dev/net none bind,create=dir 0 0
+EOF
+}
+
+function lxc_container_config_kvm() {
+ cat <<EOF
+#
+# /dev/kvm
+#
+lxc.cgroup2.devices.allow = c 10:232 rwm
+lxc.mount.entry = /dev/kvm dev/kvm none bind,create=file 0 0
+EOF
+}
+
+function lxc_container_config_loop() {
+ cat <<EOF
+#
+# /dev/loop
+#
+lxc.cgroup2.devices.allow = c 10:237 rwm
+lxc.cgroup2.devices.allow = b 7:* rwm
+lxc.mount.entry = /dev/loop-control dev/loop-control none bind,create=file 0 0
+EOF
+}
+
+function lxc_container_config_mapper() {
+ cat <<EOF
+#
+# /dev/mapper
+#
+lxc.cgroup2.devices.allow = c 10:236 rwm
+lxc.mount.entry = /dev/mapper dev/mapper none bind,create=dir 0 0
+EOF
+}
+
+function lxc_container_config_fuse() {
+ cat <<EOF
+#
+# /dev/fuse
+#
+lxc.cgroup2.devices.allow = b 10:229 rwm
+lxc.mount.entry = /dev/fuse dev/fuse none bind,create=file 0 0
+EOF
+}
+
+function lxc_container_config_kmsg() {
+ cat <<EOF
+#
+# kmsg
+#
+lxc.cgroup2.devices.allow = c 1:11 rwm
+lxc.mount.entry = /dev/kmsg dev/kmsg none bind,create=file 0 0
+EOF
+}
+
+function lxc_container_config_proc() {
+ cat <<EOF
+#
+# /proc
+#
+#
+# Only because k8s tries to write /proc/sys/vm/overcommit_memory
+# is there a way to only allow that? Would it be enough for k8s?
+#
+lxc.mount.auto = proc:rw
+EOF
+}
+
+function lxc_container_config() {
+ for config in "$@" ; do
+ case $config in
+ unprivileged)
+ ;;
+ lxc)
+ echo nesting
+ echo cap
+ ;;
+ docker)
+ echo net
+ ;;
+ libvirt)
+ echo cap
+ echo kvm
+ echo loop
+ echo mapper
+ echo fuse
+ ;;
+ k8s)
+ echo cap
+ echo loop
+ echo mapper
+ echo fuse
+ echo kmsg
+ echo proc
+ ;;
+ *)
+ echo "$config unknown ($LXC_CONTAINER_CONFIG_ALL)"
+ return 1
+ ;;
+ esac
+ done | sort -u | while read config ; do
+ echo "#"
+ echo "# include $config config snippet"
+ echo "#"
+ lxc_container_config_$config
+ done
+}
+
+function lxc_container_configure() {
+ local name="$1"
+
+ lxc_container_config $LXC_CONTAINER_CONFIG | $LXC_SUDO tee -a $(lxc_config $name)
+}
+
+function lxc_container_install_lxc_helpers() {
+ local name="$1"
+
+ $LXC_SUDO cp -a $LXC_SELF_DIR/lxc-helpers*.sh $root/$LXC_BIN
+ #
+ # Wait for the network to come up
+ #
+ local wait_networking=$(lxc_root $name)/usr/local/bin/lxc-helpers-wait-networking.sh
+ $LXC_SUDO tee $wait_networking > /dev/null <<'EOF'
+#!/bin/sh -e
+for d in $(seq 60); do
+ getent hosts wikipedia.org > /dev/null && break
+ sleep 1
+done
+getent hosts wikipedia.org > /dev/null || getent hosts wikipedia.org
+EOF
+ $LXC_SUDO chmod +x $wait_networking
+}
+
+function lxc_container_create() {
+ local name="$1"
+
+ lxc_prepare_environment
+ lxc_build_template $(lxc_template_release) "$name"
+}
+
+function lxc_container_mount() {
+ local name="$1"
+ local dir="$2"
+
+ local config=$(lxc_config $name)
+
+ if ! $LXC_SUDO grep --quiet "lxc.mount.entry = $dir" $config ; then
+ local relative_dir=${dir##/}
+ $LXC_SUDO tee -a $config > /dev/null <<< "lxc.mount.entry = $dir $relative_dir none bind,create=dir 0 0"
+ fi
+}
+
+
+function lxc_container_start() {
+ local name="$1"
+
+ if lxc_running $name ; then
+ return
+ fi
+
+ local logs
+ if $LXC_VERBOSE; then
+ logs="--logfile=/dev/tty"
+ fi
+
+ $LXC_SUDO lxc-start $logs $name
+ $LXC_SUDO lxc-wait --name $name --state RUNNING
+ lxc_container_run $name /usr/local/bin/lxc-helpers-wait-networking.sh
+}
+
+function lxc_container_stop() {
+ local name="$1"
+
+ $LXC_SUDO lxc-ls -1 --running --filter="^$name" | while read container ; do
+ $LXC_SUDO lxc-stop --kill --name="$container"
+ done
+}
+
+function lxc_container_destroy() {
+ local name="$1"
+ local root="$2"
+
+ if lxc_exists "$name" ; then
+ lxc_container_stop $name $root
+ $LXC_SUDO lxc-destroy --force --name="$name"
+ fi
+}
+
+function lxc_exists() {
+ local name="$1"
+
+ test "$($LXC_SUDO lxc-ls --filter=^$name\$)"
+}
+
+function lxc_running() {
+ local name="$1"
+
+ test "$($LXC_SUDO lxc-ls --running --filter=^$name\$)"
+}
+
+function lxc_build_template_release() {
+ local name="$(lxc_template_release)"
+
+ if lxc_exists $name ; then
+ return
+ fi
+
+ local root=$(lxc_root $name)
+ $LXC_SUDO lxc-create --name $name --template debian -- --release=$LXC_CONTAINER_RELEASE
+ echo 'lxc.apparmor.profile = unconfined' | $LXC_SUDO tee -a $(lxc_config $name)
+ lxc_container_install_lxc_helpers $name
+ lxc_container_start $name
+ lxc_container_run $name apt-get update -qq
+ lxc_apt_install $name sudo git python3
+ lxc_container_stop $name
+}
+
+function lxc_build_template() {
+ local name="$1"
+ local newname="$2"
+
+ if lxc_exists $newname ; then
+ return
+ fi
+
+ if test "$name" = "$(lxc_template_release)" ; then
+ lxc_build_template_release
+ fi
+
+ if ! $LXC_SUDO lxc-copy --name=$name --newname=$newname ; then
+ echo lxc-copy --name=$name --newname=$newname failed
+ return 1
+ fi
+ lxc_container_configure $newname
+}
+
+function lxc_apt_install() {
+ local name="$1"
+ shift
+
+ lxc_container_inside $name lxc_apt_install_inside "$@"
+}
+
+function lxc_apt_install_inside() {
+ apt-get install -y -qq "$@"
+}
+
+function lxc_install_lxc() {
+ local name="$1"
+ local prefix="$2"
+ local prefixv6="$3"
+
+ lxc_container_inside $name lxc_install_lxc_inside $prefix $prefixv6
+}
+
+function lxc_install_lxc_inside() {
+ local prefix="$1"
+ local prefixv6="${2:-$LXC_IPV6_PREFIX_DEFAULT}"
+
+ local packages="make git libvirt0 libpam-cgfs bridge-utils uidmap dnsmasq-base dnsmasq dnsmasq-utils qemu-user-static lxc-templates debootstrap"
+ if test "$(lxc_release)" = bookworm ; then
+ packages="$packages distro-info"
+ fi
+
+ lxc_apt_install_inside $packages
+
+ if ! grep --quiet LXC_ADDR=.$prefix.1. /etc/default/lxc-net ; then
+ systemctl disable --now dnsmasq
+ apt-get install -y -qq lxc
+ systemctl stop lxc-net
+ sed -i -e '/ConditionVirtualization/d' /usr/lib/systemd/system/lxc-net.service
+ systemctl daemon-reload
+ cat >> /etc/default/lxc-net <<EOF
+LXC_ADDR="$prefix.1"
+LXC_NETMASK="255.255.255.0"
+LXC_NETWORK="$prefix.0/24"
+LXC_DHCP_RANGE="$prefix.2,$prefix.254"
+LXC_DHCP_MAX="253"
+LXC_IPV6_ADDR="$prefixv6::216:3eff:fe00:1"
+LXC_IPV6_MASK="64"
+LXC_IPV6_NETWORK="$prefixv6::/64"
+LXC_IPV6_NAT="true"
+EOF
+ systemctl start lxc-net
+ fi
+}
+
+function lxc_install_docker() {
+ local name="$1"
+
+ lxc_container_inside $name lxc_install_docker_inside
+}
+
+function lxc_install_docker_inside() {
+ mkdir /etc/docker
+ cat > /etc/docker/daemon.json <<EOF
+{
+ "ipv6": true,
+ "fixed-cidr-v6": "$LXC_IPV6_DOCKER_PREFIX_DEFAULT:1::/64",
+ "default-address-pools": [
+ {"base": "$LXC_DOCKER_PREFIX_DEFAULT.0.0/16", "size": 24},
+ {"base": "$LXC_IPV6_DOCKER_PREFIX_DEFAULT:2::/104", "size": 112}
+ ]
+}
+EOF
+ lxc_apt_install_inside docker.io docker-compose
+}