From e758d8cc3da6e909512cd1d1f134ae12450f9799 Mon Sep 17 00:00:00 2001
From: Earl Warren <contact@earl-warren.org>
Date: Sun, 21 May 2023 18:38:49 +0200
Subject: [PATCH] [FORGEJO] sync lxc-helpers
 8a6034cb09215b8634eb43608c35923517506e49

(cherry picked from commit 9ede3e638c341f1792165245e5c1c065eb2c814b)
---
 pkg/runner/lxc-helpers-lib.sh | 311 ++++++++++++++++++++++++++++++++++
 pkg/runner/lxc-helpers.sh     | 136 +++++++++++++++
 2 files changed, 447 insertions(+)
 create mode 100755 pkg/runner/lxc-helpers-lib.sh
 create mode 100755 pkg/runner/lxc-helpers.sh

diff --git a/pkg/runner/lxc-helpers-lib.sh b/pkg/runner/lxc-helpers-lib.sh
new file mode 100755
index 0000000..faaae90
--- /dev/null
+++ b/pkg/runner/lxc-helpers-lib.sh
@@ -0,0 +1,311 @@
+#!/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_SUDO:=}
+: ${LXC_CONTAINER_RELEASE:=bookworm}
+: ${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_configure() {
+    local name="$1"
+    
+    $LXC_SUDO tee -a $(lxc_config $name) > /dev/null <<'EOF'
+security.nesting = true
+lxc.cap.drop =
+lxc.apparmor.profile = unconfined
+#
+# /dev/net (docker won't work without /dev/net/tun)
+#
+lxc.cgroup2.devices.allow = c 10:200 rwm
+lxc.mount.entry = /dev/net dev/net none bind,create=dir 0 0
+#
+# /dev/kvm (libvirt / kvm won't work without /dev/kvm)
+#
+lxc.cgroup2.devices.allow = c 10:232 rwm
+lxc.mount.entry = /dev/kvm dev/kvm none bind,create=file 0 0
+#
+# /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
+#
+# /dev/mapper
+#
+lxc.cgroup2.devices.allow = c 10:236 rwm
+lxc.mount.entry = /dev/mapper dev/mapper none bind,create=dir 0 0
+#
+# /dev/fuse
+#
+lxc.cgroup2.devices.allow = b 10:229 rwm
+lxc.mount.entry = /dev/fuse dev/fuse none bind,create=file 0 0
+EOF
+
+
+    #
+    # 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)
+    local packages="sudo,git,python3"
+    $LXC_SUDO lxc-create --name $name --template debian -- --release=$LXC_CONTAINER_RELEASE --packages="$packages"
+    $LXC_SUDO cp -a $LXC_SELF_DIR/lxc-helpers*.sh $root/$LXC_BIN
+    lxc_container_configure $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
+}
+
+function lxc_apt_install() {
+    local name="$1"
+    shift
+
+    lxc_container_inside $name lxc_apt_install_inside "$@"
+}
+
+function lxc_apt_install_inside() {
+    DEBIAN_FRONTEND=noninteractive apt-get install -y -qq "$@"
+}
+
+function lxc_install_lxc() {
+    local name="$1"
+    local prefix="$2"
+
+    lxc_container_inside $name lxc_install_lxc_inside $prefix
+}
+
+function lxc_install_lxc_inside() {
+    local prefix="$1"
+    
+    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 ! systemctl is-active --quiet lxc-net; then
+	systemctl disable --now dnsmasq
+	apt-get install -y -qq lxc
+	systemctl stop lxc-net
+	sed -i -e '/ConditionVirtualization/d' $root/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"
+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() {
+    lxc_apt_install_inside docker.io docker-compose
+}
diff --git a/pkg/runner/lxc-helpers.sh b/pkg/runner/lxc-helpers.sh
new file mode 100755
index 0000000..97424f0
--- /dev/null
+++ b/pkg/runner/lxc-helpers.sh
@@ -0,0 +1,136 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: MIT
+
+set -e
+
+source $(dirname $0)/lxc-helpers-lib.sh
+
+function verbose() {
+    set -x
+    PS4='${BASH_SOURCE[0]}:$LINENO: ${FUNCNAME[0]}:  '
+    LXC_VERBOSE=true
+}
+
+function help() {
+    cat <<'EOF'
+lxc-helpers.sh - LXC container management helpers
+
+SYNOPSIS
+
+   lxc-helpers.sh [-v|--verbose] [-h|--help]
+		  [-o|--os {bookworm|bullseye} (default bookworm)]
+		  command [arguments]
+
+DESCRIPTION
+
+   A thin shell based layer on top of LXC to create, populate, run and
+   destroy LXC containers. A container is created from a copy of an
+   existing container.
+
+CREATE AND DESTROY
+
+   lxc_prepare_environment
+
+       Install LXC dependencies.
+
+   lxc_container_create `name`
+
+       Create the `name` container.
+
+   lxc_container_mount `name` `path`
+
+       Configure `name` container to bind mount `path` so that it is
+       also accessible at `path` from within the container.
+
+   lxc_container_start `name`
+
+       Start the `name` container.
+
+   lxc_container_stop `name`
+
+       Stop the `name` container.
+
+   lxc_container_destroy `name`
+
+       Call lxc_container_stop `name` and destroy the container.
+
+   lxc_template_release
+
+       Echo the name of the container for the Operating System
+       specified with `--os`.
+
+   lxc_build_template `existing_container` `new_container`
+
+       Copy `existing_container` into `new_container`. If
+       `existing_container` is equal to $(lxc-helpers.sh lxc_template_release) it
+       will be created on demand.
+
+ACTIONS IN THE CONTAINER
+
+   For some command lxc_something `name` that can be called from outside the container
+   there is an equivalent function lxc_something_inside that can be called from inside
+   the container.
+
+   lxc_install_lxc `name` `prefix`
+   lxc_install_lxc_inside `prefix`
+
+      Install LXC in the `name` container to allow the creation of
+      named containers. `prefix` is a class C IP prefix from which
+      containers will obtain their IP (for instance 10.40.50).
+
+   lxc_container_run `name` command [options...]
+
+      Run the `command` within the `name` container.
+
+   lxc_container_run_script `name` `path`
+   lxc_container_run_script_as `name` `user` `path`
+
+      Run the script found at `path` within the `name` container. The
+      environment is cleared before running the script. The first form
+      will run as root, the second form will impersonate `user`.
+
+   lxc_container_user_install `name` `user_id` `user` [`homedir` default `/home`]
+
+      Create the `user` with `user_id` in the `name` container with a
+      HOME at `/homedir/user`. Passwordless sudo permissions are
+      granted to `user`. It is made a member of the groups docker, kvm
+      and libvirt if they exist already. A SSH key is created.
+
+      Example: lxc_container_user_install mycontainer $(id -u) $USER
+
+EOF
+}
+
+function main() {
+    local options=$(getopt -o hvo --long help,verbose,os: -- "$@")
+    [ $? -eq 0 ] || {
+	echo "Incorrect options provided"
+	exit 1
+    }
+    eval set -- "$options"
+    while true; do
+	case "$1" in
+	    -v | --verbose)
+		verbose
+		;;
+	    -h | --help)
+		help
+		;;
+	    -o | --os)
+		LXC_CONTAINER_RELEASE=$2
+		shift
+		;;
+	    --)
+		shift
+		break
+		;;
+	esac
+	shift
+    done
+
+    lxc_maybe_sudo
+
+    "$@"
+}
+
+main "$@"