Files
dante_beacon/dep/dante_package/dep.sh
2025-12-12 16:27:31 +01:00

281 lines
8.7 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/bin/sh
# To use a different OCI-compliant container runtime,
# update both CONTAINER_RUNTIME and CONTAINER_RUNTIME_PATH:
#
# - CONTAINER_RUNTIME should be set to the name of the runtime binary (e.g., 'crun', 'runc').
# - CONTAINER_RUNTIME_PATH should point to the directory where the binary is installed
# (e.g., '/usr/bin' for a system-installed runtime).
CONTAINER_RUNTIME_PATH=$PWD
CONTAINER_RUNTIME=crun
CONTAINER_STATUS_PATH=/run/$CONTAINER_RUNTIME
CONTAINER_CMD="$CONTAINER_RUNTIME_PATH/$CONTAINER_RUNTIME --root=$CONTAINER_STATUS_PATH"
CONTAINER_CMD_ADDITIONAL_OPTIONS=""
CONTAINER_LOGS="/var/log/dante_container.log"
IMAGES_PATH=$PWD/dante_data/images
ROOTFS_MOUNTPOINT=$PWD/bundle/rootfs
ACTIVE_IMAGE_ID_PATH=$IMAGES_PATH/active
DANTE_JSON=$PWD/dante_data/capability/dante.json
# Check we can actually start/stop containers
# NOTE: on some systems 'id' might not be available, hence we check manually
if [ "$(grep -E '^Uid:' /proc/self/status | awk '{print $2}')" -ne 0 ]; then
echo "This script must be executed with root privileges."
echo ""
exit 1
fi
# This function assumes that:
# - the JSON file is well-formed
# - key and value appear on the same line
# - strings are double-quoted and dont contain escaped quotes
# - assumes the key exists exactly once per line
get_json_field()
{
json_file=$1
field_name=$2
default="__NOT_FOUND__"
if [ ! -f "$json_file" ]; then
echo "error: file '$json_file' not found" >&2
exit 1
fi
# explaining each sed:
# - 's/^[^:]*://' removes everything up to and including the first colon
# - 's/ //' removes the first space character after the colon, if present
# - 's/^"//' and 's/"$//' removes leading and trailing double quotes from the value
# - 's/,[[:space:]]*$//' removes a trailing comma and any following whitespace (e.g. to handle lists)
# - 's/[[:space:]]*$//' trims any remaining trailing whitespace from the value
value=$(grep "\"$field_name\"" "$json_file" | \
sed -e 's/^[^:]*://' -e 's/ //' | \
sed -e 's/^"//' -e 's/"$//' | \
sed -e 's/,[[:space:]]*$//' | \
sed -e 's/[[:space:]]*$//' | head -n 1)
if [ -z "$value" ]; then
echo "$default"
else
echo "$value"
fi
}
check_cgroup_mounts()
{
cgroup_version=$(get_json_field "$DANTE_JSON" "cgroupVersion")
if [ "$cgroup_version" = "__NOT_FOUND__" ]; then
return
fi
check_mount() {
path=$1
expectedType=$2
while IFS= read -r line; do
# get the second field (mount point) and the third field (type)
mountPoint=$(echo "$line" | awk '{print $2}')
mountType=$(echo "$line" | awk '{print $3}')
# if the mountPoint doesn't start with /sys/fs/cgroup, skip it
case "$mountPoint" in
/sys/fs/cgroup*) ;;
*) continue ;;
esac
# if mount point and type exactly match the expected values, we're good
if [ "$mountPoint" = "$path" ] && [ "$mountType" = "$expectedType" ]; then
echo "mount OK: $path ($expectedType)"
return
fi
# There is a chance multiple controllers are mounted on the same path,
# for instance we might be looking for /sys/fs/cgroup/cpu and have
#
# /sys/fs/cgroup/cpu,cpuacct cgroup etc..
#
# mounted instead.
# because we skip entries that do not start with /sys/fs/cgroup at
# the beginning of the loop, we know getting the substring after
# /sys/fs/cgroup at this point will yield an empty string at worst
cgroupSubstring=${mountPoint#/sys/fs/cgroup/}
# do the same with $path
cgroupPathSubstring=${path#/sys/fs/cgroup/}
# check if cgroupPathSubstring is part of cgroupSubstring
# eg this would successfully match 'cpu' against both 'cpuacct,cpu' and 'cpu,cpuacct'
if echo "$cgroupSubstring" | grep -qw "$cgroupPathSubstring"; then
if [ "$mountType" = "$expectedType" ]; then
echo "mount OK: $path ($expectedType)"
return
fi
fi
done < /proc/mounts
echo "warning: missing or incorrect mountpoint: $path (expected type: $expectedType)"
}
if [ "$cgroup_version" = "1" ]; then
echo "cgroup version set to v1 in $DANTE_JSON"
echo "checking mounts..."
check_mount "/sys/fs/cgroup" "tmpfs"
check_mount "/sys/fs/cgroup/cpuset" "cgroup"
check_mount "/sys/fs/cgroup/cpu" "cgroup"
check_mount "/sys/fs/cgroup/memory" "cgroup"
check_mount "/sys/fs/cgroup/devices" "cgroup"
elif [ "$cgroup_version" = "2" ]; then
echo "cgroup version set to v2 in $DANTE_JSON"
echo "checking mounts..."
check_mount "/sys/fs/cgroup" "cgroup2"
else
echo "error: unsupported cgroupVersion value ($cgroup_version) in $DANTE_JSON"
exit 1
fi
}
start()
{
# A poorly-timed stop() could leave the container mounted while
# the processes inside the container were successfully shut down.
# Instead of relying on whether the container is there or not when
# deciding to start DEP, check whether dep_manager is actually running.
# shellcheck disable=SC2009
# (SC2009 recommends using pgrep, but it is not always available)
if ps -e >/dev/null 2>&1; then
PS_CMD="ps -e"
else
PS_CMD="ps" # assume heavily stripped-down BusyBox
fi
if $PS_CMD | grep -q "[d]ep_manager"; then
echo "DEP is already running"
exit 0
fi
# Some basic checks before proceeding
if [ ! -f "$ACTIVE_IMAGE_ID_PATH" ]; then
echo "error: $ACTIVE_IMAGE_ID_PATH not found, can't select active rootfs"
exit 1
fi
active_image_id=$(cat "$ACTIVE_IMAGE_ID_PATH")
rootfs="$IMAGES_PATH/$active_image_id/rootfs_squash"
if [ ! -f "$rootfs" ]; then
echo "error: $rootfs not found"
exit 1
fi
check_cgroup_mounts
mkdir -p /var/run/dante
mkdir -p ${CONTAINER_STATUS_PATH}
# Make sure /etc/resolv.conf is there when later on we
# try to bind mount it from the container.
if [ ! -f "/etc/resolv.conf" ]; then
touch /etc/resolv.conf
fi
if ! grep -q " $ROOTFS_MOUNTPOINT " /proc/mounts; then
if ! mount "$rootfs" "$ROOTFS_MOUNTPOINT" >/dev/null 2>&1; then
echo "error: could not mount $rootfs"
exit 1
fi
fi
# At this point, it's safe to always forcefully delete the container.
#
# This may be necessary in scenarios where the DEP processes did not actually
# start after running ./dep.sh start — for example, due to invalid configuration
# in dante.json. In such cases, a user would typically inspect the logs,
# fix the underlying issue, and then retry with ./dep.sh start.
#
# However, if the dante container remains mounted, the container runtime's 'run'
# command will fail, forcing the user to manually delete the container - either
# by using ./dep.sh stop (which is not intuitive) or manually.
#
# To avoid these issues and make the recovery easier to execute, unconditionally
# remove the dante container before attempting to run it again.
#
# NOTE: while we could check whether the container exists before removing it,
# not all systems provide the necessary cgroup status layers to reliably list
# configured containers.
${CONTAINER_CMD} delete --force dante
# rootfs (only mount with no parent mount) cannot be pivot_root()ed. The check hereafter
# relies on the fact that rootfs will be either a ramfs or tmpfs. This is a bit more restrictive
# than necessary, as the container could in practice be started from a ramfs or tmpfs (as long as
# it is not the rootfs).
# WARNING: crun falls back to chroot when --no-pivot is enabled, and a process running in the container
# can in practice access the tree outside of the chroot.
ROOT_FSTYPE=$(mount|grep 'on / type'|awk '{print $5}')
if [ "$ROOT_FSTYPE" = "rootfs" ] || [ "$ROOT_FSTYPE" = "ramfs" ] || [ "$ROOT_FSTYPE" = "tmpfs" ]; then
CONTAINER_CMD_ADDITIONAL_OPTIONS="$CONTAINER_CMD_ADDITIONAL_OPTIONS --no-pivot"
fi
if ! ${CONTAINER_CMD} run ${CONTAINER_CMD_ADDITIONAL_OPTIONS} --detach --bundle ./bundle dante > "$CONTAINER_LOGS" 2>&1; then
echo "error: failed to start dante container, more details available in $CONTAINER_LOGS"
exit 1
else
echo "DEP started"
fi
}
stop()
{
# in some cases we might have the mountpoint but no container running:
# check if that's the case before proceeding
if ${CONTAINER_CMD} list | grep dante >/dev/null 2>&1; then
# stop the init process (dep_manager) by sending a SIGTERM signal
echo "stopping DEP..."
${CONTAINER_CMD} kill dante TERM
for _ in $(seq 1 10); do
sleep 1
DEP_PROCS=$(${CONTAINER_CMD} ps dante | grep -v PID -c)
if [ "$DEP_PROCS" -eq 0 ]; then
break
fi
done
DEP_PROCS=$(${CONTAINER_CMD} ps dante | grep -v PID -c)
if [ "$DEP_PROCS" -ne 0 ]; then
echo "DEP still running, sending SIGKILL"
${CONTAINER_CMD} kill -a dante KILL
sleep 1
fi
echo "removing container..."
${CONTAINER_CMD} delete --force dante
fi
if grep -q " $ROOTFS_MOUNTPOINT " /proc/mounts; then
echo "umount rootfs..."
umount "$PWD"/bundle/rootfs
fi
echo "done"
}
USAGE_MESSAGE="Usage: dep.sh <start|stop>"
if [ "$#" -eq 0 ]; then
echo "$USAGE_MESSAGE"
exit 1
fi
case $1 in
"start" ) start "$2" ;;
"stop" ) stop ;;
* )
echo "$USAGE_MESSAGE"
exit 1
;;
esac