diff options
Diffstat (limited to '')
-rwxr-xr-x | libexec/container/auto | 167 | ||||
-rwxr-xr-x | libexec/container/build | 287 | ||||
-rwxr-xr-x | libexec/container/console | 119 | ||||
-rwxr-xr-x | libexec/container/enter | 129 | ||||
-rwxr-xr-x | libexec/container/get | 287 | ||||
-rwxr-xr-x | libexec/container/info | 221 | ||||
-rwxr-xr-x | libexec/container/key | 176 | ||||
-rwxr-xr-x | libexec/container/limit | 212 | ||||
-rwxr-xr-x | libexec/container/list | 461 | ||||
-rwxr-xr-x | libexec/container/log | 167 | ||||
-rwxr-xr-x | libexec/container/move | 215 | ||||
-rwxr-xr-x | libexec/container/rebuild | 152 | ||||
-rwxr-xr-x | libexec/container/remove | 261 | ||||
-rwxr-xr-x | libexec/container/restart | 172 | ||||
-rwxr-xr-x | libexec/container/run | 131 | ||||
-rwxr-xr-x | libexec/container/start | 570 | ||||
-rwxr-xr-x | libexec/container/status | 108 | ||||
-rwxr-xr-x | libexec/container/stop | 360 | ||||
-rwxr-xr-x | libexec/container/top | 122 | ||||
-rwxr-xr-x | libexec/container/update | 270 | ||||
-rwxr-xr-x | libexec/container/version | 49 |
21 files changed, 4636 insertions, 0 deletions
diff --git a/libexec/container/auto b/libexec/container/auto new file mode 100755 index 0000000..83c5c50 --- /dev/null +++ b/libexec/container/auto @@ -0,0 +1,167 @@ +#!/bin/sh + +# Copyright (C) 2014-2022 Daniel Baumann <daniel.baumann@open-infrastructure.net> +# +# SPDX-License-Identifier: GPL-3.0+ +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. + +set -e + +PROJECT="open-infrastructure" +SOFTWARE="compute-tools" +PROGRAM="container" +COMMAND="$(basename ${0})" + +CONFIG="/etc/${SOFTWARE}/config" +HOOKS="/etc/${SOFTWARE}/hooks" + +Parameters () +{ + GETOPT_LONGOPTIONS="force,start,stop," + GETOPT_OPTIONS="f,s,t," + + PARAMETERS="$(getopt --longoptions ${GETOPT_LONGOPTIONS} --name=${COMMAND} --options ${GETOPT_OPTIONS} --shell sh -- ${@})" + + if [ "${?}" != "0" ] + then + echo "'${COMMAND}': getopt exit" >&2 + exit 1 + fi + + eval set -- "${PARAMETERS}" + + while true + do + case "${1}" in + -f|--force) + FORCE="true" + shift 1 + ;; + + -s|--start) + ACTION="start" + shift 1 + ;; + + -t|--stop) + ACTION="stop" + shift 1 + ;; + + --) + shift 1 + break + ;; + + *) + echo "'${COMMAND}': getopt error" >&2 + exit 1 + ;; + esac + done +} + +Usage () +{ + echo "Usage: ${PROGRAM} ${COMMAND} -f|--force -s|--start -t|--stop" >&2 + echo + echo "See ${COMMAND}(1), ${PROGRAM}(1) and ${PROJECT}(7) for more information." + + exit 1 +} + +Parameters "${@}" + +if [ -z "${ACTION}" ] +then + Usage +fi + +HOST="$(hostname -f 2> /dev/null || hostname)" + +# Pre hooks +for FILE in "${HOOKS}/pre-${COMMAND}".* "${HOOKS}/${NAME}.pre-${COMMAND}" +do + if [ -x "${FILE}" ] + then + "${FILE}" + fi +done + +# Run +OPTIONS="" + +case "${FORCE}" in + true) + OPTIONS="${OPTIONS} -f" + ;; +esac + +for FILE in "${CONFIG}"/*.conf +do + if ! grep -Eqs "^ *cnt.container-server=${HOST}" "${FILE}" + then + continue + fi + + CONTAINER="$(basename ${FILE} .conf)" + CNT_AUTO="$(grep -Es "^ *cnt.auto=" ${FILE} | awk -F= '{ print $2 }')" + + case "${ACTION}" in + start) + case "${CNT_AUTO}" in + force-true) + OPTIONS="${OPTIONS} -f" + + cnt ${ACTION} -n ${CONTAINER} ${OPTIONS} || true + ;; + + last-on) + if grep -qs start "/var/lib/${SOFTWARE}/state/${CONTAINER}.run" || \ + [ ! -e "/var/lib/${SOFTWARE}/state/${CONTAINER}.run" ] + then + cnt start -n ${CONTAINER} ${OPTIONS} -f || true + fi + ;; + + last-off) + if grep -qs start "/var/lib/${SOFTWARE}/state/${CONTAINER}.run" + then + cnt start -n ${CONTAINER} ${OPTIONS} -f || true + fi + ;; + + true) + cnt ${ACTION} -n ${CONTAINER} ${OPTIONS} || true + ;; + esac + ;; + + stop) + OPTIONS="${OPTIONS} -f --stateless" + + cnt ${ACTION} -n ${CONTAINER} ${OPTIONS} || true + ;; + esac +done + +# Post hooks +for FILE in "${HOOKS}/post-${COMMAND}".* "${HOOKS}/${NAME}.post-${COMMAND}" +do + if [ -x "${FILE}" ] + then + "${FILE}" + fi +done diff --git a/libexec/container/build b/libexec/container/build new file mode 100755 index 0000000..2c29730 --- /dev/null +++ b/libexec/container/build @@ -0,0 +1,287 @@ +#!/bin/sh + +# Copyright (C) 2014-2022 Daniel Baumann <daniel.baumann@open-infrastructure.net> +# +# SPDX-License-Identifier: GPL-3.0+ +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. + +set -e + +PROJECT="open-infrastructure" +SOFTWARE="compute-tools" +PROGRAM="container" +COMMAND="$(basename ${0})" + +CONFIG="/etc/${SOFTWARE}/config" +HOOKS="/etc/${SOFTWARE}/hooks" +MACHINES="/var/lib/machines" +SCRIPTS="/usr/share/${SOFTWARE}/build-scripts" +CONFIG_TEMPLATE="/usr/share/${SOFTWARE}/config/container.conf.in" + +Parameters () +{ + GETOPT_LONGOPTIONS="name:,cnt.auto:,cnt.container-server:,cnt.overlay:,cnt.overlay-options:,cnt.start:,bind:,bind-ro:,capability:,drop-capability:,script:,verbose," + GETOPT_OPTIONS="n:,b:,c:,d:,s:,v," + + PARAMETERS="$(getopt --longoptions ${GETOPT_LONGOPTIONS} --name=${COMMAND} --options ${GETOPT_OPTIONS} --shell sh -- ${@})" + + if [ "${?}" != "0" ] + then + echo "'${COMMAND}': getopt exit" >&2 + exit 1 + fi + + eval set -- "${PARAMETERS}" + + while true + do + case "${1}" in + -n|--name) + NAME="${2}" + shift 2 + ;; + + --cnt.auto) + CNT_AUTO="${2}" + shift 2 + ;; + + --cnt.container-server) + CNT_CONTAINER_SERVER="${2}" + shift 2 + ;; + + --cnt.overlay) + CNT_OVERLAY="${2}" + shift 2 + ;; + + --cnt.overlay-options) + CNT_OVERLAY_OPTIONS="${2}" + shift 2 + ;; + + --cnt.start) + CNT_START="${2}" + shift 2 + ;; + + -b|--bind) + BIND="${2}" + shift 2 + ;; + + --bind-ro) + BIND_RO="${2}" + shift 2 + ;; + + -c|--capability) + CAPABILITY="${2}" + shift 2 + ;; + + -d|--drop-capability) + DROP_CAPABILITY="${2}" + shift 2 + ;; + + -s|--script) + SCRIPT="${2}" + shift 2 + ;; + + -v|--verbose) + VERBOSE="true" + shift 1 + ;; + + --) + shift 1 + break + ;; + + *) + echo "'${COMMAND}': getopt error" >&2 + exit 1 + ;; + esac + done +} + +Usage () +{ + echo "Usage: ${PROGRAM} ${COMMAND} -n|--name NAME [--cnt.auto=true|false|force-true|last-on|last-off] [--cnt.container-server=true|false|FQDN] [--cnt.overlay=DIRECTORY_LOWER:DIRECTORY_UPPER:DIRECTORY_WORK:DIRECTORY_MERGED] [--cnt.overlay-options=OPTION[,OPTION]] [--cnt.start=OPTION[,OPTION]] [-b|--bind DIRECTORY:DIRECTORY[:OPTIONS]] [--bind-ro DIRECTORY:DIRECTORY[:OPTIONS]] [-c|--capability CAPABILITY[,CAPABILITY]] [-d|--drop-capability DROP_CAPABILITY[,DROP_CAPABILITY]] [-s|--script SCRIPT] [-v|--verbose] [-- SCRIPT_OPTIONS]" >&2 + echo + echo "See ${COMMAND}(1), ${PROGRAM}(1) and ${PROJECT}(7) for more information." + + exit 1 +} + +Parameters "${@}" + +if [ -z "${NAME}" ] +then + Usage +fi + +case "${NAME}" in + ALL) + echo "'${NAME}': name 'ALL' is reserved to expand to all available container" >&2 + exit 1 + ;; +esac + +if [ -e "${CONFIG}/${NAME}.conf" ] +then + echo "'${NAME}': container already exists or ${CONFIG}/${NAME}.conf has not been removed" >&2 + exit 1 +fi + +if [ -z "${SCRIPT}" ] +then + if [ -e "${SCRIPTS}/default" ] + then + TARGET="$(basename $(readlink ${SCRIPTS}/default))" + + case "${TARGET}" in + container_build-script) + TARGET="$(basename $(readlink /etc/alternatives/container_build-script))" + ;; + esac + + if [ -e "${SCRIPTS}/${TARGET}" ] + then + SCRIPT="${TARGET}" + else + echo "default -> '${TARGET}': no such script" >&2 + exit 1 + fi + else + SCRIPT="debian" + fi +else + if [ ! -e "${SCRIPTS}/${SCRIPT}" ] + then + echo "'${SCRIPT}': no such script" >&2 + exit 1 + fi +fi + +case "${VERBOSE}" in + true) + +cat << EOF +################################################################################ +Building container: ${NAME} +################################################################################ +EOF + + ;; +esac + +CNT_CONTAINER_SERVER="${CNT_CONTAINER_SERVER:-$(hostname -f 2> /dev/null || hostname)}" + +# Pre hooks +for FILE in "${HOOKS}/pre-${COMMAND}".* "${HOOKS}/${NAME}.pre-${COMMAND}" +do + if [ -x "${FILE}" ] + then + "${FILE}" + fi +done + +# Creating rw bind mounts +if [ -n "${BIND}" ] +then + BINDS="$(echo ${BIND} | sed -e 's|;| |g')" + + for ENTRY in ${BINDS} + do + DIRECTORY="$(echo ${ENTRY} | awk -F: '{ print $1 }')" + + mkdir -p "${DIRECTORY}" + done +fi + +# Creating ro bind mounts +if [ -n "${BIND_RO}" ] +then + BINDS_RO="$(echo ${BIND_RO} | sed -e 's|;| |g')" + + for ENTRY in ${BINDS_RO} + do + DIRECTORY="$(echo ${ENTRY} | awk -F: '{ print $1 }')" + + mkdir -p "${DIRECTORY}" + done +fi + +# Creating overlay mounts +if [ -n "${CNT_OVERLAY}" ] +then + CNT_OVERLAYS="$(echo ${CNT_OVERLAY} | sed -e 's|;| |g')" + + for ENTRY in ${CNT_OVERLAYS} + do + DIRECTORY_LOWER="$(echo ${ENTRY} | awk -F: '{ print $1 }')" + DIRECTORY_UPPER="$(echo ${ENTRY} | awk -F: '{ print $2 }')" + DIRECTORY_WORK="$(echo ${ENTRY} | awk -F: '{ print $3 }')" + DIRECTORY_MERGED="$(echo ${ENTRY} | awk -F: '{ print $4 }')" + + for DIRECTORY in "${DIRECTORY_LOWER}" "${DIRECTORY_UPPER}" "${DIRECTORY_WORK}" "${DIRECTORY_MERGED}" + do + mkdir -p "${DIRECTORY}" + done + done +fi + +# config +mkdir -p "${CONFIG}" + +sed -e "s|@CNT_AUTO@|${CNT_AUTO}|g" \ + -e "s|@CNT_CONTAINER_SERVER@|${CNT_CONTAINER_SERVER}|g" \ + -e "s|@CNT_NETWORK_BRIDGE@|${CNT_NETWORK_BRIDGE}|g" \ + -e "s|@CNT_OVERLAY@|${CNT_OVERLAY}|g" \ + -e "s|@CNT_OVERLAY_OPTIONS@|${CNT_OVERLAY_OPTIONS}|g" \ + -e "s|@CNT_START@|${CNT_START}|g" \ + -e "s|@NAME@|${NAME}|g" \ + -e "s|@BIND@|${BIND}|g" \ + -e "s|@BIND_RO@|${BIND_RO}|g" \ + -e "s|@BOOT@|yes|g" \ + -e "s|@CAPABILITY@|${CAPABILITY}|g" \ + -e "s|@DIRECTORY@|${MACHINES}/${NAME}|g" \ + -e "s|@DROP_CAPABILITY@|${DROP_CAPABILITY}|g" \ + -e "s|@LINK_JOURNAL@|no|g" \ + -e "s|@MACHINE@|${NAME}|g" \ + -e "s|@NETWORK_VETH_EXTRA@|${NETWORK_VETH_EXTRA}|g" \ + -e "s|@PRIVATE_USERS@|no|g" \ + -e "s|@REGISTER@|yes|g" \ +"${CONFIG_TEMPLATE}" > "${CONFIG}/${NAME}.conf" + +# Run +"${SCRIPTS}/${SCRIPT}" $(echo "${@}" | sed -e 's| -- | |') + +# Post hooks +for FILE in "${HOOKS}/post-${COMMAND}".* "${HOOKS}/${NAME}.post-${COMMAND}" +do + if [ -x "${FILE}" ] + then + "${FILE}" + fi +done + +# done +echo "'${NAME}': container built." diff --git a/libexec/container/console b/libexec/container/console new file mode 100755 index 0000000..be2b897 --- /dev/null +++ b/libexec/container/console @@ -0,0 +1,119 @@ +#!/bin/sh + +# Copyright (C) 2014-2022 Daniel Baumann <daniel.baumann@open-infrastructure.net> +# +# SPDX-License-Identifier: GPL-3.0+ +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. + +set -e + +PROJECT="open-infrastructure" +SOFTWARE="compute-tools" +PROGRAM="container" +COMMAND="$(basename ${0})" + +HOOKS="/etc/${SOFTWARE}/hooks" +MACHINES="/var/lib/machines" + +Parameters () +{ + GETOPT_LONGOPTIONS="name:," + GETOPT_OPTIONS="n:," + + PARAMETERS="$(getopt --longoptions ${GETOPT_LONGOPTIONS} --name=${COMMAND} --options ${GETOPT_OPTIONS} --shell sh -- ${@})" + + if [ "${?}" != "0" ] + then + echo "'${COMMAND}': getopt exit" >&2 + exit 1 + fi + + eval set -- "${PARAMETERS}" + + while true + do + case "${1}" in + -n|--name) + NAME="${2}" + shift 2 + ;; + + --) + shift 1 + break + ;; + + *) + echo "'${COMMAND}': getopt error" >&2 + exit 1 + ;; + esac + done +} + +Usage () +{ + echo "Usage: ${PROGRAM} ${COMMAND} -n|--name NAME" >&2 + echo + echo "See ${COMMAND}(1), ${PROGRAM}(1) and ${PROJECT}(7) for more information." + + exit 1 +} + +Parameters "${@}" + +if [ -z "${NAME}" ] +then + Usage +fi + +if [ ! -e "${MACHINES}/${NAME}" ] +then + echo "'${NAME}': no such container" >&2 + exit 1 +fi + +STATE="$(machinectl show ${NAME} 2>&1 | awk -FState= '/^State=/ { print $2 }')" + +case "${STATE}" in + running) + ;; + + *) + echo "'${NAME}': container is not started" >&2 + exit 1 + ;; +esac + +# Pre hooks +for FILE in "${HOOKS}/pre-${COMMAND}".* "${HOOKS}/${NAME}.pre-${COMMAND}" +do + if [ -x "${FILE}" ] + then + "${FILE}" + fi +done + +# Run +machinectl login ${NAME} + +# Post hooks +for FILE in "${HOOKS}/post-${COMMAND}".* "${HOOKS}/${NAME}.post-${COMMAND}" +do + if [ -x "${FILE}" ] + then + "${FILE}" + fi +done diff --git a/libexec/container/enter b/libexec/container/enter new file mode 100755 index 0000000..b366ba6 --- /dev/null +++ b/libexec/container/enter @@ -0,0 +1,129 @@ +#!/bin/sh + +# Copyright (C) 2014-2022 Daniel Baumann <daniel.baumann@open-infrastructure.net> +# +# SPDX-License-Identifier: GPL-3.0+ +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. + +set -e + +PROJECT="open-infrastructure" +SOFTWARE="compute-tools" +PROGRAM="container" +COMMAND="$(basename ${0})" + +HOOKS="/etc/${SOFTWARE}/hooks" +MACHINES="/var/lib/machines" + +Parameters () +{ + GETOPT_LONGOPTIONS="name:," + GETOPT_OPTIONS="n:," + + PARAMETERS="$(getopt --longoptions ${GETOPT_LONGOPTIONS} --name=${COMMAND} --options ${GETOPT_OPTIONS} --shell sh -- ${@})" + + if [ "${?}" != "0" ] + then + echo "'${COMMAND}': getopt exit" >&2 + exit 1 + fi + + eval set -- "${PARAMETERS}" + + while true + do + case "${1}" in + -n|--name) + NAME="${2}" + shift 2 + ;; + + --) + shift 1 + break + ;; + + *) + echo "'${COMMAND}': getopt error" >&2 + exit 1 + ;; + esac + done +} + +Usage () +{ + echo "Usage: ${PROGRAM} ${COMMAND} -n|--name NAME" >&2 + echo + echo "See ${COMMAND}(1), ${PROGRAM}(1) and ${PROJECT}(7) for more information." + + exit 1 +} + +Parameters "${@}" + +if [ -z "${NAME}" ] +then + Usage +fi + +if [ ! -e "${MACHINES}/${NAME}" ] +then + echo "'${NAME}': no such container" >&2 + exit 1 +fi + +STATE="$(machinectl show ${NAME} 2>&1 | awk -FState= '/^State=/ { print $2 }')" + +case "${STATE}" in + running) + ;; + + *) + echo "'${NAME}': container is not running" >&2 + exit 1 + ;; +esac + +LEADER="$(machinectl status ${NAME} | awk '/Leader: / { print $2 }')" + +# Pre hooks +for FILE in "${HOOKS}/pre-${COMMAND}".* "${HOOKS}/${NAME}.pre-${COMMAND}" +do + if [ -x "${FILE}" ] + then + "${FILE}" + fi +done + +SSH_CLIENT="${SSH_CLIENT:-127.0.0.1 0 0}" + +# Run +nsenter --all --target "${LEADER}" --wd="${MACHINES}/${NAME}/root" /usr/bin/script -c "LC_ALL=C.UTF-8 SSH_CLIENT=\"${SSH_CLIENT}\" /bin/bash -l" -q /dev/null + +case "${SSH_CLIENT}" in + 127.0.0.1*) + unset SSH_CLIENT + ;; +esac + +# Post hooks +for FILE in "${HOOKS}/post-${COMMAND}".* "${HOOKS}/${NAME}.post-${COMMAND}" +do + if [ -x "${FILE}" ] + then + "${FILE}" + fi +done diff --git a/libexec/container/get b/libexec/container/get new file mode 100755 index 0000000..0d0f420 --- /dev/null +++ b/libexec/container/get @@ -0,0 +1,287 @@ +#!/bin/sh + +# Copyright (C) 2014-2022 Daniel Baumann <daniel.baumann@open-infrastructure.net> +# +# SPDX-License-Identifier: GPL-3.0+ +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. + +set -e + +PROJECT="open-infrastructure" +SOFTWARE="compute-tools" +PROGRAM="container" +COMMAND="$(basename ${0})" + +CONFIG="/etc/${SOFTWARE}/config" +HOOKS="/etc/${SOFTWARE}/hooks" +MACHINES="/var/lib/machines" +SCRIPTS="/usr/share/${SOFTWARE}/get-scripts" +CONFIG_TEMPLATE="/usr/share/${SOFTWARE}/config/container.conf.in" + +Parameters () +{ + GETOPT_LONGOPTIONS="name:,cnt.container-server:,cnt.overlay:,cnt.overlay-options:,start:,bind:,bind-ro:,capability:,drop-capability:,script:,verbose," + GETOPT_OPTIONS="n:,b:,c:,d:,s:,v," + + PARAMETERS="$(getopt --longoptions ${GETOPT_LONGOPTIONS} --name=${COMMAND} --options ${GETOPT_OPTIONS} --shell sh -- ${@})" + + if [ "${?}" != "0" ] + then + echo "'${COMMAND}': getopt exit" >&2 + exit 1 + fi + + eval set -- "${PARAMETERS}" + + while true + do + case "${1}" in + -n|--name) + NAME="${2}" + shift 2 + ;; + + --cnt.auto) + CNT_AUTO="${2}" + shift 2 + ;; + + --cnt.container-server) + CNT_CONTAINER_SERVER="${2}" + shift 2 + ;; + + --cnt.overlay) + CNT_OVERLAY="${2}" + shift 2 + ;; + + --cnt.overlay-options) + CNT_OVERLAY_OPTIONS="${2}" + shift 2 + ;; + + --cnt.start) + CNT_START="${2}" + shift 2 + ;; + + -b|--bind) + BIND="${2}" + shift 2 + ;; + + --bind-ro) + BIND_RO="${2}" + shift 2 + ;; + + -c|--capability) + CAPABILITY="${2}" + shift 2 + ;; + + -d|--drop-capability) + DROP_CAPABILITY="${2}" + shift 2 + ;; + + -s|--script) + SCRIPT="${2}" + shift 2 + ;; + + -v|--verbose) + VERBOSE="true" + shift 1 + ;; + + --) + shift 1 + break + ;; + + *) + echo "'${COMMAND}': getopt error" >&2 + exit 1 + ;; + esac + done +} + +Usage () +{ + echo "Usage: ${PROGRAM} ${COMMAND} -n|--name NAME [--cnt.container-server=true|false|FQDN] [--cnt.overlay=DIRECTORY_LOWER:DIRECTORY_UPPER:DIRECTORY_WORK:DIRECTORY_MERGED] [--cnt.overlay-options=OPTION[,OPTION]] [--cnt.start=OPTION[,OPTION]] [-b|--bind DIRECTORY:DIRECTORY[:OPTIONS]] [--bind-ro DIRECTORY:DIRECTORY[:OPTIONS]] [-c|--capability CAPABILITY[,CAPABILITY]] [-d|--drop-capability DROP_CAPABILITY[,DROP_CAPABILITY]] [-s|--script SCRIPT] [-v|--verbose] [-- SCRIPT_OPTIONS]" >&2 + echo + echo "See ${COMMAND}(1), ${PROGRAM}(1) and ${PROJECT}(7) for more information." + + exit 1 +} + +Parameters "${@}" + +if [ -z "${NAME}" ] +then + Usage +fi + +case "${NAME}" in + ALL) + echo "'${NAME}': name 'ALL' is reserved to expand to all available container" >&2 + exit 1 + ;; +esac + +if [ -e "${CONFIG}/${NAME}.conf" ] +then + echo "'${NAME}': container already exists or ${CONFIG}/${NAME}.conf has not been removed" >&2 + exit 1 +fi + +if [ -z "${SCRIPT}" ] +then + if [ -e "${SCRIPTS}/default" ] + then + TARGET="$(basename $(readlink ${SCRIPTS}/default))" + + case "${TARGET}" in + container_get-script) + TARGET="$(basename $(readlink /etc/alternatives/container_get-script))" + ;; + esac + + if [ -e "${SCRIPTS}/${TARGET}" ] + then + SCRIPT="${TARGET}" + else + echo "default -> '${TARGET}': no such script" >&2 + exit 1 + fi + else + SCRIPT="curl" + fi +else + if [ ! -e "${SCRIPTS}/${SCRIPT}" ] + then + echo "'${SCRIPT}': no such script" >&2 + exit 1 + fi +fi + +case "${VERBOSE}" in + true) + +cat << EOF +################################################################################ +Building container: ${NAME} +################################################################################ +EOF + + ;; +esac + +CNT_CONTAINER_SERVER="${CNT_CONTAINER_SERVER:-$(hostname -f 2> /dev/null || hostname)}" + +# Pre hooks +for FILE in "${HOOKS}/pre-${COMMAND}".* "${HOOKS}/${NAME}.pre-${COMMAND}" +do + if [ -x "${FILE}" ] + then + "${FILE}" + fi +done + +# Creating rw bind mounts +if [ -n "${BIND}" ] +then + BINDS="$(echo ${BIND} | sed -e 's|;| |g')" + + for ENTRY in ${BINDS} + do + DIRECTORY="$(echo ${ENTRY} | awk -F: '{ print $1 }')" + + mkdir -p "${DIRECTORY}" + done +fi + +# Creating ro bind mounts +if [ -n "${BIND_RO}" ] +then + BINDS_RO="$(echo ${BIND_RO} | sed -e 's|;| |g')" + + for ENTRY in ${BINDS_RO} + do + DIRECTORY="$(echo ${ENTRY} | awk -F: '{ print $1 }')" + + mkdir -p "${DIRECTORY}" + done +fi + +# Creating overlay mounts +if [ -n "${CNT_OVERLAY}" ] +then + CNT_OVERLAYS="$(echo ${CNT_OVERLAY} | sed -e 's|;| |g')" + + for ENTRY in ${CNT_OVERLAYS} + do + DIRECTORY_LOWER="$(echo ${ENTRY} | awk -F: '{ print $1 }')" + DIRECTORY_UPPER="$(echo ${ENTRY} | awk -F: '{ print $2 }')" + DIRECTORY_WORK="$(echo ${ENTRY} | awk -F: '{ print $3 }')" + DIRECTORY_MERGED="$(echo ${ENTRY} | awk -F: '{ print $4 }')" + + for DIRECTORY in "${DIRECTORY_LOWER}" "${DIRECTORY_UPPER}" "${DIRECTORY_WORK}" "${DIRECTORY_MERGED}" + do + mkdir -p "${DIRECTORY}" + done + done +fi + +# config +mkdir -p "${CONFIG}" + +sed -e "s|@CNT_AUTO@|${CNT_AUTO}|g" \ + -e "s|@CNT_CONTAINER_SERVER@|${CNT_CONTAINER_SERVER}|g" \ + -e "s|@CNT_NETWORK_BRIDGE@|${CNT_NETWORK_BRIDGE}|g" \ + -e "s|@CNT_OVERLAY@|${CNT_OVERLAY}|g" \ + -e "s|@CNT_OVERLAY_OPTIONS@|${CNT_OVERLAY_OPTIONS}|g" \ + -e "s|@CNT_START@|${CNT_START}|g" \ + -e "s|@NAME@|${NAME}|g" \ + -e "s|@BIND@|${BIND}|g" \ + -e "s|@BIND_RO@|${BIND_RO}|g" \ + -e "s|@BOOT@|yes|g" \ + -e "s|@CAPABILITY@|${CAPABILITY}|g" \ + -e "s|@DIRECTORY@|${MACHINES}/${NAME}|g" \ + -e "s|@DROP_CAPABILITY@|${DROP_CAPABILITY}|g" \ + -e "s|@LINK_JOURNAL@|no|g" \ + -e "s|@MACHINE@|${NAME}|g" \ + -e "s|@NETWORK_VETH_EXTRA@|${NETWORK_VETH_EXTRA}|g" \ + -e "s|@PRIVATE_USERS@|no|g" \ + -e "s|@REGISTER@|yes|g" \ +"${CONFIG_TEMPLATE}" > "${CONFIG}/${NAME}.conf" + +# Run +"${SCRIPTS}/${SCRIPT}" $(echo "${@}" | sed -e 's| -- | |') + +# Post hooks +for FILE in "${HOOKS}/post-${COMMAND}".* "${HOOKS}/${NAME}.post-${COMMAND}" +do + if [ -x "${FILE}" ] + then + "${FILE}" + fi +done + +# done +echo "'${NAME}': container created." diff --git a/libexec/container/info b/libexec/container/info new file mode 100755 index 0000000..b713e7a --- /dev/null +++ b/libexec/container/info @@ -0,0 +1,221 @@ +#!/bin/sh + +# Copyright (C) 2014-2022 Daniel Baumann <daniel.baumann@open-infrastructure.net> +# +# SPDX-License-Identifier: GPL-3.0+ +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. + +set -e + +PROJECT="open-infrastructure" +SOFTWARE="compute-tools" +PROGRAM="container" +COMMAND="$(basename ${0})" + +CONFIG="/etc/${SOFTWARE}/config" +HOOKS="/etc/${SOFTWARE}/hooks" +MACHINES="/var/lib/machines" + +VERSION="$(${PROGRAM} version)" + +Parameters () +{ + GETOPT_LONGOPTIONS="name:,status,os,ip," + GETOPT_OPTIONS="n:," + + PARAMETERS="$(getopt --longoptions ${GETOPT_LONGOPTIONS} --name=${COMMAND} --options ${GETOPT_OPTIONS} --shell sh -- ${@})" + + if [ "${?}" != "0" ] + then + echo "'${COMMAND}': getopt exit" >&2 + exit 1 + fi + + eval set -- "${PARAMETERS}" + + while true + do + case "${1}" in + -n|--name) + NAME="${2}" + shift 2 + ;; + + --status) + ACTIONS="${ACTIONS} status" + shift 1 + ;; + + --os) + ACTIONS="${ACTIONS} os" + shift 1 + ;; + + --ip) + ACTIONS="${ACTIONS} ip" + shift 1 + ;; + + --) + shift 1 + break + ;; + + *) + echo "'${COMMAND}': getopt error" >&2 + exit 1 + ;; + esac + done +} + +Usage () +{ + echo "Usage: ${PROGRAM} ${COMMAND} -n|--name NAME [--status] [--os] [--ip]" >&2 + echo + echo "See ${COMMAND}(1), ${PROGRAM}(1) and ${PROJECT}(7) for more information." + + exit 1 +} + +Parameters "${@}" + +# Pre hooks +for FILE in "${HOOKS}/pre-${COMMAND}".* "${HOOKS}/${NAME}.pre-${COMMAND}" +do + if [ -x "${FILE}" ] + then + "${FILE}" + fi +done + +ACTIONS="${ACTIONS:-status os ip}" +HOST="$(cat /etc/hostname)" + +# Run + +# Status +STATUS="$(machinectl show ${NAME} 2>&1 | awk -FState= '/^State=/ { print $2 }')" + +if [ -e "${CONFIG}/${NAME}.conf" ] +then + CONTAINER_SERVER="$(awk -Fcnt.container-server= '/^cnt.container-server=/ { print $2 }' ${CONFIG}/${NAME}.conf)" + CONTAINER_SERVER="${CONTAINER_SERVER:-false}" + + case "${CONTAINER_SERVER}" in + ${HOST}|true) + ;; + + *) + STATUS="other" + ;; + esac +else + STATUS="other" +fi + +case "${STATUS}" in + running) + STATUS="started" + ;; + + other) + ;; + + *) + STATUS="stopped" + ;; +esac + +# OS +VERSION_BASH="$(chroot ${MACHINES}/${NAME} apt-cache policy bash | awk '/Installed: / { print $2 }')" + +case "${VERSION_BASH}" in + 4.1-*|4.1.[0-9]*) + OS="Debian 6 (squeeze)" + ;; + + 4.2-*|4.2.[0-9]*) + OS="Debian 7 (wheezy)" + ;; + + 4.3-*|4.3.[0-9]*) + OS="Debian 8 (jessie)" + ;; + + 4.4-*|4.4.[0-9]*) + OS="Debian 9 (stretch)" + ;; + + 5.0-*|5.0.[0-9]*) + OS="Debian 10 (buster)" + ;; + + 5.1-*|5.1.[0-9]*) + OS="Debian 11 (bullseye)" + ;; + + 5.2-*|5.2.[0-9]*) + OS="Debian 12 (bookworm)" + ;; + + *) + OS="n/a" + ;; +esac + +case "${STATUS}" in + started) + IP="$(cnt run -n ${NAME} -- hostname -I)" + ;; + + *) + if ls "${MACHINES}/${NAME}/etc/systemd/network"/*.network > /dev/null 2>&1 + then + IP="$(awk -FAddress= '/^Address/ { printf "%s ", $2 }' ${MACHINES}/${NAME}/etc/systemd/network/*.network)" + elif [ -e "${MACHINES}/${NAME}/etc/network/interfaces" ] + then + IP="$(awk '/address/ { printf "%s ", $2 }' ${MACHINES}/${NAME}/etc/network/interfaces)" + fi + + IP="${IP:-n/a}" + ;; +esac + +for ACTION in ${ACTIONS} +do + case "${ACTION}" in + status) + echo "${STATUS}" + ;; + + os) + echo "${OS}" + ;; + + ip) + echo "${IP}" + ;; + esac +done + +# Post hooks +for FILE in "${HOOKS}/post-${COMMAND}".* "${HOOKS}/${NAME}.post-${COMMAND}" +do + if [ -x "${FILE}" ] + then + "${FILE}" + fi +done diff --git a/libexec/container/key b/libexec/container/key new file mode 100755 index 0000000..efd214e --- /dev/null +++ b/libexec/container/key @@ -0,0 +1,176 @@ +#!/bin/sh + +# Copyright (C) 2014-2022 Daniel Baumann <daniel.baumann@open-infrastructure.net> +# +# SPDX-License-Identifier: GPL-3.0+ +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. + +set -e + +PROJECT="open-infrastructure" +SOFTWARE="compute-tools" +PROGRAM="container" +COMMAND="$(basename ${0})" + +KEYS="/etc/${SOFTWARE}/keys" + +Parameters () +{ + GETOPT_LONGOPTIONS="add:,list,remove:," + GETOPT_OPTIONS="a:,l,r:," + + PARAMETERS="$(getopt --longoptions ${GETOPT_LONGOPTIONS} --name=${COMMAND} --options ${GETOPT_OPTIONS} --shell sh -- ${@})" + + if [ "${?}" != "0" ] + then + echo "'${COMMAND}': getopt exit" >&2 + exit 1 + fi + + eval set -- "${PARAMETERS}" + + while true + do + case "${1}" in + -a|--add) + ADD="${2}" + ACTION="add" + shift 2 + ;; + + -l|--list) + ACTION="list" + shift 1 + ;; + + -r|--remove) + REMOVE="${2}" + ACTION="remove" + shift 2 + ;; + + --) + shift 1 + break + ;; + + *) + echo "'${COMMAND}': getopt error" >&2 + exit 1 + ;; + esac + done +} + +Usage () +{ + echo "Usage: ${PROGRAM} ${COMMAND} [-a|--add KEY_FILE|KEY_ID] [-l|--list] [-r|--remove KEY|KEY_ID]" >&2 + echo + echo "See ${COMMAND}(1), ${PROGRAM}(1) and ${PROJECT}(7) for more information." + + exit 1 +} + +Parameters "${@}" + +if [ -z "${ACTION}" ] +then + Usage +fi + +if [ ! -w "${KEYS}" ] +then + if [ "$(id -u)" -ne 0 ] + then + echo "'${COMMAND}': need root privileges (or write permissions to '${KEYS}')" >&2 + exit 1 + fi +fi + +# Pre hooks +for FILE in "${HOOKS}/pre-${COMMAND}".* "${HOOKS}/${NAME}.pre-${COMMAND}" +do + if [ -x "${FILE}" ] + then + "${FILE}" + fi +done + +# Run +if [ ! -e "${KEYS}" ] +then + mkdir -p "${KEYS}" + + chown root:root "${KEYS}" + chmod 0700 "${KEYS}" + +cat > "${KEYS}/gnupg.conf" << EOF +keyserver hkps://keys.openpgp.org +keyserver-options include-revoked +keyserver-options no-honor-keyserver-url + +cert-digest-algo SHA512 +default-preference-list SHA512 SHA384 SHA256 SHA224 AES256 AES192 AES ZLIB ZIP Uncompressed +personal-cipher-preferences AES256 AES192 AES +personal-compress-preferences ZLIB ZIP Uncompressed +personal-digest-preferences SHA512 SHA384 SHA256 SHA224 + +no-comments +no-emit-version +no-greeting +keyid-format 0xlong +list-options show-keyring +list-options show-uid-validity +verify-options show-uid-validity +with-fingerprint + +charset utf-8 +EOF + +fi + +case "${ACTION}" in + add) + if [ -e "${ADD}" ] + then + gpg --homedir "${KEYS}" --import "${ADD}" + elif [ -e "/usr/share/${SOFTWARE}/keys/${ADD}" ] + then + gpg --homedir "${KEYS}" --import "/usr/share/${SOFTWARE}/keys/${ADD}" + elif [ -e "/usr/share/${SOFTWARE}/keys/${ADD}.pub" ] + then + gpg --homedir "${KEYS}" --import "/usr/share/${SOFTWARE}/keys/${ADD}.pub" + else + gpg --homedir "${KEYS}" --recv "${ADD}" + fi + ;; + + list) + gpg --homedir "${KEYS}" --list-keys + ;; + + remove) + gpg --homedir "${KEYS}" --delete-keys "${REMOVE}" + ;; +esac + +# Post hooks +for FILE in "${HOOKS}/post-${COMMAND}".* "${HOOKS}/${NAME}.post-${COMMAND}" +do + if [ -x "${FILE}" ] + then + "${FILE}" + fi +done diff --git a/libexec/container/limit b/libexec/container/limit new file mode 100755 index 0000000..b7f6e9b --- /dev/null +++ b/libexec/container/limit @@ -0,0 +1,212 @@ +#!/bin/sh + +# Copyright (C) 2014-2022 Daniel Baumann <daniel.baumann@open-infrastructure.net> +# +# SPDX-License-Identifier: GPL-3.0+ +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. + +set -e + +PROJECT="open-infrastructure" +SOFTWARE="compute-tools" +PROGRAM="container" +COMMAND="$(basename ${0})" + +HOOKS="/etc/${SOFTWARE}/hooks" +MACHINES="/var/lib/machines" + +Parameters () +{ + GETOPT_LONGOPTIONS="name:,blockio-device-weight:,blockio-read-bandwidth:,blockio-weight:,blockio-write-bandwidth:,cpu-quota:,cpu-shares:,memory-limit:,tasks-max:," + GETOPT_OPTIONS="n:b:c:m:t:," + + PARAMETERS="$(getopt --longoptions ${GETOPT_LONGOPTIONS} --name=${COMMAND} --options ${GETOPT_OPTIONS} --shell sh -- ${@})" + + if [ "${?}" != "0" ] + then + echo "'${COMMAND}': getopt exit" >&2 + exit 1 + fi + + eval set -- "${PARAMETERS}" + + while true + do + case "${1}" in + -n|--name) + NAME="${2}" + shift 2 + ;; + + -c|--cpu-quota) + CPU_QUOTA="${2}" + shift 2 + ;; + + --cpu-shares) + CPU_SHARES="${2}" + shift 2 + ;; + + -m|--memory-limit) + MEMORY_LIMIT="${2}" + shift 2 + ;; + + -t|--tasks-max) + TASKS_MAX="${2}" + shift 2 + ;; + + --blockio-device-weight) + BLOCK_IO_DEVICE_WEIGHT="${2}" + shift 2 + ;; + + --blockio-read-bandwidth) + BLOCK_IO_READ_BANDWIDTH="${2}" + shift 2 + ;; + + -b|--blockio-weight) + BLOCK_IO_WEIGHT="${2}" + shift 2 + ;; + + --blockio-write-bandwidth) + BLOCK_IO_WRITE_BANDWIDTH="${2}" + shift 2 + ;; + + --) + shift 1 + break + ;; + + *) + echo "'${COMMAND}': getopt error" >&2 + exit 1 + ;; + esac + done +} + +Usage () +{ + echo "Usage: ${PROGRAM} ${COMMAND} -n|--name NAME [--blockio-device-weight \"DEVICE WEIGHT\"] [--blockio-read-bandwidth \"DEVICE BYTES\"] [-b|--blockio-weight WEIGHT] [--blockio-write-bandwidth \"DEVICE BYTES\"] [-c|--cpu-quota QUOTA] [--cpu-shares SHARES] [-m|--memory-limit BYTES] [-t|--tasks-max NUMBER]" >&2 + echo + echo "See ${COMMAND}(1), ${PROGRAM}(1) and ${PROJECT}(7) for more information." + + exit 1 +} + +Parameters "${@}" + +if [ -z "${NAME}" ] +then + Usage +fi + +if [ ! -e "${MACHINES}/${NAME}" ] +then + echo "'${NAME}': no such container" >&2 + exit 1 +fi + +STATE="$(machinectl show ${NAME} 2>&1 | awk -FState= '/^State=/ { print $2 }')" + +case "${STATE}" in + running) + ;; + + *) + echo "'${NAME}': container is not running" >&2 + exit 1 + ;; +esac + +if [ -n "${BLOCK_IO_DEVICE_WEIGHT}" ] +then + BLOCK_IO_DEVICE_WEIGHT="BlockIODeviceWeight=${BLOCK_IO_DEVICE_WEIGHT}" + SET_PROPERTY="true" +fi + +if [ -n "${BLOCK_IO_READ_BANDWIDTH}" ] +then + BLOCK_IO_READ_BANDWIDTH="BlockIOReadBandwidth=${BLOCK_IO_READ_BANDWIDTH}" + SET_PROPERTY="true" +fi + +if [ -n "${BLOCK_IO_WEIGHT}" ] +then + BLOCK_IO_WEIGHT="BlockIOWeight=${BLOCK_IO_WEIGHT}" + SET_PROPERTY="true" +fi + +if [ -n "${BLOCK_IO_WRITE_BANDWIDTH}" ] +then + BLOCK_IO_WRITE_BANDWIDTH="BlockIOReadBandwidth=${BLOCK_IO_WRITE_BANDWIDTH}" + SET_PROPERTY="true" +fi + +if [ -n "${CPU_QUOTA}" ] +then + CPU_QUOTA="CPUQuota=${CPU_QUOTA}" + SET_PROPERTY="true" +fi + +if [ -n "${CPU_SHARES}" ] +then + CPU_SHARES="CPUShares=${CPU_SHARES}" + SET_PROPERTY="true" +fi + +if [ -n "${MEMORY_LIMIT}" ] +then + MEMORY_LIMIT="MemoryLimit=${MEMORY_LIMIT}" + SET_PROPERTY="true" +fi + +if [ -n "${TASKS_MAX}" ] +then + TASKS_MAX="TasksMax=${TASKS_MAX}" + SET_PROPERTY="true" +fi + +if [ "${SET_PROPERTY}" != "true" ] +then + Usage +fi + +# Pre hooks +for FILE in "${HOOKS}/pre-${COMMAND}".* "${HOOKS}/${NAME}.pre-${COMMAND}" +do + if [ -x "${FILE}" ] + then + "${FILE}" + fi +done + +# Run +systemctl --runtime set-property ${NAME} ${BLOCK_IO_DEVICE_WEIGHT} ${BLOCK_IO_READ_BANDWIDTH} ${BLOCK_IO_WEIGHT} ${BLOCK_IO_WRITE_BANDWIDTH} ${CPU_QUOTA} ${CPU_SHARES} ${MEMORY_LIMIT} ${TASKS_MAX} + +# Post hooks +for FILE in "${HOOKS}/post-${COMMAND}".* "${HOOKS}/${NAME}.post-${COMMAND}" +do + if [ -x "${FILE}" ] + then + "${FILE}" + fi +done diff --git a/libexec/container/list b/libexec/container/list new file mode 100755 index 0000000..a56c1f8 --- /dev/null +++ b/libexec/container/list @@ -0,0 +1,461 @@ +#!/bin/sh + +# Copyright (C) 2014-2022 Daniel Baumann <daniel.baumann@open-infrastructure.net> +# +# SPDX-License-Identifier: GPL-3.0+ +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. + +set -e + +PROJECT="open-infrastructure" +SOFTWARE="compute-tools" +PROGRAM="container" +COMMAND="$(basename ${0})" + +CONFIG="/etc/${SOFTWARE}/config" +HOOKS="/etc/${SOFTWARE}/hooks" +MACHINES="/var/lib/machines" + +VERSION="$(${PROGRAM} version)" + +Parameters () +{ + GETOPT_LONGOPTIONS="all,csv-separator:,format:,host:,nwdiag-color:,nwdiag-label:,other,started,stopped," + GETOPT_OPTIONS="a,f:,h:,o,s,t," + + PARAMETERS="$(getopt --longoptions ${GETOPT_LONGOPTIONS} --name=${COMMAND} --options ${GETOPT_OPTIONS} --shell sh -- ${@})" + + if [ "${?}" != "0" ] + then + echo "'${COMMAND}': getopt exit" >&2 + exit 1 + fi + + eval set -- "${PARAMETERS}" + + while true + do + case "${1}" in + -a|--all) + LIST="${LIST} all" + shift 1 + ;; + + --csv-separator) + CSV_SEPARATOR="${2}" + shift 2 + ;; + + -f|--format) + FORMAT="${2}" + shift 2 + ;; + + -h|--host) + HOST="${2}" + shift 2 + ;; + + --nwdiag-color) + NWDIAG_COLOR="${2}" + shift 2 + ;; + + --nwdiag-label) + NWDIAG_LABEL="${2}" + shift 2 + ;; + + -o|--other) + LIST="${LIST} other" + shift 1 + ;; + + -s|--started) + LIST="${LIST} started" + shift 1 + ;; + + -t|--stopped) + LIST="${LIST} stopped" + shift 1 + ;; + + --) + shift 1 + break + ;; + + *) + echo "'${COMMAND}': getopt error" >&2 + exit 1 + ;; + esac + done +} + +Usage () +{ + echo "Usage: ${PROGRAM} ${COMMAND} [-a|--all] [--csv-separator SEPARATOR] [--format FORMAT] [-h|--host HOSTNAME] [--nwdiag-color COLOR] [--nwdiag-label LABEL] [-o|--other] [-s|--started] [-t|--stopped]" >&2 + echo + echo "See ${COMMAND}(1), ${PROGRAM}(1) and ${PROJECT}(7) for more information." + + exit 1 +} + +Parameters "${@}" + +LIST="${LIST:-started stopped}" +FORMAT="${FORMAT:-cli}" +HOST="${HOST:-$(hostname -f 2> /dev/null || hostname)}" + +CSV_SEPARATOR="${CSV_SEPARATOR:-,}" + +# Pre hooks +for FILE in "${HOOKS}/pre-${COMMAND}".* "${HOOKS}/${NAME}.pre-${COMMAND}" +do + if [ -x "${FILE}" ] + then + "${FILE}" + fi +done + +# Run +List () +{ + case "${FORMAT}" in + cli) + printf "%1s %-74s %-19s\n" "${STATUS}" "${BLUE}${CONTAINER}${NORMAL}" "${YELLOW}${ADDRESS}${NORMAL}" + ;; + + csv) + echo "${HOST}${CSV_SEPARATOR}${CONTAINER}${CSV_SEPARATOR}${STATE}${CSV_SEPARATOR}${ADDRESS}" + ;; + + json) + +cat << EOF + { + "name": "${CONTAINER}", + "status": "${STATE}", + "address": "${ADDRESS}", + }, +EOF + + ;; + + nwdiag) + Nwdiag_width "${CONTAINER}" + + case "${STATE}" in + started) + COLOR="#73d216" + ;; + + stopped) + COLOR="#cc0000" + ;; + + other) + COLOR="#d3d7cf" + ;; + esac + + echo " ${CONTAINER} [address = \"${ADDRESS}\", color = \"${COLOR}\", shape = \"flowchart.terminator\", width = \"${WIDTH}\"];" + ;; + + shell|sh) + echo "${CONTAINER}" + ;; + + yaml) + +cat << EOF + - name: ${CONTAINER} + address: ${ADDRESS} +EOF + + ;; + + xml) + +cat << EOF + <container> + <name>${CONTAINER}</name> + <address>${ADDRESS}</address> + </container> +EOF + + ;; + esac +} + +Nwdiag_width () +{ + NAME="${1}" + + CHARACTERS="$(echo "${NAME}" | wc -c)" + + if [ "${CHARACTERS}" -gt 13 ] + then + # default width is 128 fitting 13 characters + WIDTH="$(( ${CHARACTERS} - 13 ))" + WIDTH="$(( ${WIDTH} * 5 ))" + WIDTH="$(( ${WIDTH} + 128 ))" + else + WIDTH="128" + fi +} + +case "${FORMAT}" in + cli) + RED="$(tput setaf 1)$(tput bold)" + GREEN="$(tput setaf 2)$(tput bold)" + YELLOW="$(tput setaf 3)$(tput bold)" + BLUE="$(tput setaf 4)$(tput bold)" + WHITE="$(tput setaf 7)$(tput bold)" + NORMAL="$(tput sgr0)" + +cat << EOF +${WHITE} ${NORMAL} Container IP Address +-------------------------------------------------------------------------------- +EOF + + ;; + + csv) + +cat << EOF +# compute-tools ${VERSION} +Host${CSV_SEPARATOR}Container${CSV_SEPARATOR}Status${CSV_SEPARATOR}IPv4-Address +EOF + + ;; + + json) + +cat << EOF +{ + "compute-tools": { + "version": "${VERSION}", + }, + "host": { + "name": "${HOST}", + }, + "container": [ +EOF + + ;; + + nwdiag) + NETWORK="$(echo ${HOST} | sed -e 's|\.|_|g')" + +cat << EOF +# compute-tools ${VERSION} +nwdiag { + external_connector = none; + network ${NETWORK} { +EOF + + if [ -n "${NWDIAG_LABEL}" ] + then + echo " label = \"${NWDIAG_LABEL}\"" + else + echo " label = \"\"" + fi + + NUMBER="$(${0} --format=short | wc -l)" + + if [ -n "${NWDIAG_COLOR}" ] + then + COLOR="${NWDIAG_COLOR}" + else + COLOR="#3465a4" + fi + + Nwdiag_width "${HOST}" + +cat << EOF + # host + ${HOST} [color = "${COLOR}", shape = "box", numbered = "${NUMBER}", width = "${WIDTH}"]; + # container +EOF + + ;; + + yaml) + +cat << EOF +--- +compute_tools: + version: ${VERSION} + +host: + name: ${HOST} + +container: +EOF + + ;; + + xml) + +cat << EOF +<compute-tools> + <version>${VERSION}</version> +</compute-tools> +<host> + <name>${HOST}</name> +</host> +<containers> +EOF + + ;; +esac + +if ls "${MACHINES}"/* > /dev/null 2>&1 +then + CONTAINERS="$(cd "${MACHINES}" 2>/dev/null && find -maxdepth 1 \( -type d -or -type l \) -and -not -name 'lost+found' -printf '%P\n' | sort)" +fi + +for CONTAINER in ${CONTAINERS} +do + STATE="$(machinectl show ${CONTAINER} 2>&1 | awk -FState= '/^State=/ { print $2 }')" + + if [ -e "${CONFIG}/${CONTAINER}.conf" ] + then + CONTAINER_SERVER="$(awk -Fcnt.container-server= '/^cnt.container-server=/ { print $2 }' ${CONFIG}/${CONTAINER}.conf)" + CONTAINER_SERVER="${CONTAINER_SERVER:-false}" + + case "${CONTAINER_SERVER}" in + ${HOST}|true) + ;; + + *) + STATE="other" + ;; + esac + else + STATE="other" + fi + + case "${STATE}" in + running) + STATE="started" + STATUS="${GREEN}●${NORMAL}" + ;; + + other) + STATUS="${WHITE}○${NORMAL}" + ;; + + *) + STATE="stopped" + STATUS="${RED}●${NORMAL}" + ;; + esac + + ADDRESS="" + + case "${STATE}" in + started) + case "${FORMAT}" in + shell|sh) + ;; + + *) + LEADER="$(machinectl status ${CONTAINER} | awk '/Leader: / { print $2 }')" + ADDRESS="$(nsenter --all --target "${LEADER}" /bin/hostname -I)" + ;; + esac + + ADDRESS="${ADDRESS:-none}" + ;; + + *) + if ls "${MACHINES}/${CONTAINER}/etc/systemd/network"/*.network > /dev/null 2>&1 + then + ADDRESS="$(for IP in $(awk -FAddress= '/^Address/ { print $2 }' ${MACHINES}/${CONTAINER}/etc/systemd/network/*.network); do echo -n "${IP} "; done)" + elif [ -e "${MACHINES}/${CONTAINER}/etc/network/interfaces" ] + then + ADDRESS="$(for IP in $(awk '/address/ { print $2 }' ${MACHINES}/${CONTAINER}/etc/network/interfaces); do echo -n "${IP} "; done)" + fi + + ADDRESS="${ADDRESS:-n/a}" + ;; + esac + + if echo ${LIST} | grep -qs all + then + List + fi + + for ITEM in other started stopped + do + if echo ${LIST} | grep -qs ${ITEM} + then + case "${STATE}" in + ${ITEM}) + List + ;; + esac + fi + done +done + +case "${FORMAT}" in + cli) + +cat << EOF +-------------------------------------------------------------------------------- +EOF + + ;; + + json) + +cat << EOF + ], +} +EOF + + ;; + + nwdiag) + +cat << EOF + } +} +EOF + + ;; + + xml) + +cat << EOF +</containers> +EOF + + ;; +esac + +# Post hooks +for FILE in "${HOOKS}/post-${COMMAND}".* "${HOOKS}/${NAME}.post-${COMMAND}" +do + if [ -x "${FILE}" ] + then + "${FILE}" + fi +done diff --git a/libexec/container/log b/libexec/container/log new file mode 100755 index 0000000..b7a000d --- /dev/null +++ b/libexec/container/log @@ -0,0 +1,167 @@ +#!/bin/sh + +# Copyright (C) 2014-2022 Daniel Baumann <daniel.baumann@open-infrastructure.net> +# +# SPDX-License-Identifier: GPL-3.0+ +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. + +set -e + +PROJECT="open-infrastructure" +SOFTWARE="compute-tools" +PROGRAM="container" +COMMAND="$(basename ${0})" + +HOOKS="/etc/${SOFTWARE}/hooks" +LOG="/var/log/${SOFTWARE}/${PROGRAM}.log" + +Parameters () +{ + GETOPT_LONGOPTIONS="name:,date:," + GETOPT_OPTIONS="n:,d:," + + PARAMETERS="$(getopt --longoptions ${GETOPT_LONGOPTIONS} --name=${COMMAND} --options ${GETOPT_OPTIONS} --shell sh -- ${@})" + + if [ "${?}" != "0" ] + then + echo "'${COMMAND}': getopt exit" >&2 + exit 1 + fi + + eval set -- "${PARAMETERS}" + + while true + do + case "${1}" in + -d|--date) + DATE="${2}" + shift 2 + ;; + + -n|--name) + NAME="${2}" + shift 2 + ;; + + -u|--user) + USER="${2}" + shift 2 + ;; + + --) + shift 1 + break + ;; + + *) + echo "'${COMMAND}': getopt error" >&2 + exit 1 + ;; + esac + done +} + +Usage () +{ + echo "Usage: ${PROGRAM} ${COMMAND} [-n|--name NAME] [-d|--date DATE|today|today-N|yesterday] [-u|--user USER]" >&2 + echo + echo "See ${COMMAND}(1), ${PROGRAM}(1) and ${PROJECT}(7) for more information." + + exit 1 +} + +Parameters "${@}" + +# Pre hooks +for FILE in "${HOOKS}/pre-${COMMAND}".* +do + if [ -x "${FILE}" ] + then + "${FILE}" + fi +done + +# Run +case "${DATE}" in + today-*) + DAYS="$(echo ${DATE} | awk -F- '{ print $2 }')" + + START="$(date -d "${DAYS} day ago" '+%Y-%m-%d')" + END="$(date +%Y-%m-%d)" + + DATE="(${START}" + + while true + do + DATE="${DATE}|${START}" + + if [ "${START}" = "${END}" ] + then + break + fi + + START="$(date +%Y-%m-%d --date "${START} +1 day")" + done + + DATE="${DATE})" + ;; + + today) + DATE="$(date +%Y-%m-%d)" + ;; + + yesterday) + DATE="$(date -d '1 day ago' '+%Y-%m-%d')" + ;; +esac + +if ls "${LOG}"-*.gz > /dev/null 2>&1 +then + LOGS="$(ls ${LOG}-*.gz) ${LOG}" +else + LOGS="${LOG}" +fi + +for LOG in ${LOGS} +do + case "${LOG}" in + *.gz) + GREP="zgrep" + ;; + + *) + GREP="grep" + ;; + esac + + case "${NAME}" in + "") + ${GREP} -E "^${DATE}" "${LOG}" || true + ;; + + *) + ${GREP} -E "^${DATE}" "${LOG}" | grep " ${NAME}" || true + ;; + esac +done + +# Post hooks +for FILE in "${HOOKS}/post-${COMMAND}".* +do + if [ -x "${FILE}" ] + then + "${FILE}" + fi +done diff --git a/libexec/container/move b/libexec/container/move new file mode 100755 index 0000000..fdc19e6 --- /dev/null +++ b/libexec/container/move @@ -0,0 +1,215 @@ +#!/bin/sh + +# Copyright (C) 2014-2022 Daniel Baumann <daniel.baumann@open-infrastructure.net> +# +# SPDX-License-Identifier: GPL-3.0+ +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. + +set -e + +PROJECT="open-infrastructure" +SOFTWARE="compute-tools" +PROGRAM="container" +COMMAND="$(basename ${0})" + +CONFIG="/etc/${SOFTWARE}/config" +HOOKS="/etc/${SOFTWARE}/hooks" +MACHINES="/var/lib/machines" + +Parameters () +{ + GETOPT_LONGOPTIONS="force,new:,old:," + GETOPT_OPTIONS="f,n:,o:," + + PARAMETERS="$(getopt --longoptions ${GETOPT_LONGOPTIONS} --name=${COMMAND} --options ${GETOPT_OPTIONS} --shell sh -- ${@})" + + if [ "${?}" != "0" ] + then + echo "'${COMMAND}': getopt exit" >&2 + exit 1 + fi + + eval set -- "${PARAMETERS}" + + while true + do + case "${1}" in + -f|--force) + FORCE="true" + shift 1 + ;; + + -n|--new) + NEW="${2}" + shift 2 + ;; + + -o|--old) + OLD="${2}" + shift 2 + ;; + + --) + shift 1 + break + ;; + + *) + echo "'${COMMAND}': getopt error" >&2 + exit 1 + ;; + esac + done +} + +Usage () +{ + echo "Usage: ${PROGRAM} ${COMMAND} [-f|--force] -n|--new NAME -o|--old NAME" >&2 + echo + echo "See ${COMMAND}(1), ${PROGRAM}(1) and ${PROJECT}(7) for more information." + + exit 1 +} + +Parameters "${@}" + +if [ -z "${OLD}" ] || [ -z "${NEW}" ] +then + Usage +fi + +if [ ! -e "${MACHINES}/${OLD}" ] +then + echo "'${OLD}': no such container" >&2 + exit 1 +fi + +if [ -e "${MACHINES}/${NEW}" ] +then + echo "'${NEW}': container already exists" >&2 + exit 1 +fi + +STATE="$(machinectl show ${OLD} 2>&1 | awk -FState= '/^State=/ { print $2 }')" + +case "${STATE}" in + running) + echo "'${OLD}': container is started" >&2 + exit 1 + ;; +esac + +case "${FORCE}" in + true) + ;; + + *) + if ${PROGRAM} list --other | grep -qs "^${OLD}$" + then + echo -n "'${OLD}': rename container to '${NEW}' [y|N]? " + read FORCE + + FORCE="$(echo ${FORCE} | tr '[A-Z]' '[a-z]')" + + case "${FORCE}" in + y|yes) + ;; + + *) + exit 1 + ;; + esac + fi + ;; +esac + +# Pre hooks +for FILE in "${HOOKS}/pre-${COMMAND}".* "${HOOKS}/${OLD}.pre-${COMMAND}" +do + if [ -x "${FILE}" ] + then + "${FILE}" + fi +done + +# Run +mv "${CONFIG}/${OLD}.conf" "${CONFIG}/${NEW}.conf" +mv "${MACHINES}/${OLD}" "${MACHINES}/${NEW}" + +# rw bind mounts +BIND="$(awk -Fbind= '/^bind=/ { print $2 }' ${CONFIG}/${NEW}.conf)" + +if [ -n "${BIND}" ] +then + BINDS="$(echo ${BIND} | sed -e 's|;| |g')" + + for BIND in ${BINDS} + do + SOURCE_OLD="$(echo ${BIND} | awk -F: '{ print $1 }')" + SOURCE_NEW="$(echo ${SOURCE_OLD} | sed -e "s|${OLD}|${NEW}|g")" + + if [ "${SOURCE_OLD}" != "${SOURCE_NEW}" ] + then + mv "${SOURCE_OLD}" "${SOURCE_NEW}" + fi + + TARGET_OLD="$(echo ${BIND} | awk -F: '{ print $2 }')" + TARGET_NEW="$(echo ${TARGET_OLD} | sed -e "s|${OLD}|${NEW}|g")" + + if [ "${TARGET_OLD}" != "${TARGET_NEW}" ] + then + mv "${MACHINES}/${NEW}/${TARGET_OLD}" "${MACHINES}/${NEW}/${TARGET_NEW}" + fi + done +fi + +# ro bind mounts +BIND_RO="$(awk -Fbind-ro= '/^bind-ro=/ { print $2 }' ${CONFIG}/${NEW}.conf)" + +if [ -n "${BIND_RO}" ] +then + BINDS_RO="$(echo ${BIND_RO} | sed -e 's|;| |g')" + + for BIND_RO in ${BINDS_RO} + do + SOURCE_OLD="$(echo ${BIND_RO} | awk -F: '{ print $1 }')" + SOURCE_NEW="$(echo ${SOURCE_OLD} | sed -e "s|${OLD}|${NEW}|g")" + + if [ "${SOURCE_OLD}" != "${SOURCE_NEW}" ] + then + mv "${SOURCE_OLD}" "${SOURCE_NEW}" + fi + + TARGET_OLD="$(echo ${BIND_RO} | awk -F: '{ print $2 }')" + TARGET_NEW="$(echo ${TARGET_OLD} | sed -e "s|${OLD}|${NEW}|g")" + + if [ "${TARGET_OLD}" != "${TARGET_NEW}" ] + then + mv "${MACHINES}/${NEW}/${TARGET_OLD}" "${MACHINES}/${NEW}/${TARGET_NEW}" + fi + done +fi + +# config +sed -i -e "s|${OLD}|${NEW}|g" "${CONFIG}/${NEW}.conf" + +# Post hooks +for FILE in "${HOOKS}/post-${COMMAND}".* "${HOOKS}/${NEW}.post-${COMMAND}" +do + if [ -x "${FILE}" ] + then + "${FILE}" + fi +done diff --git a/libexec/container/rebuild b/libexec/container/rebuild new file mode 100755 index 0000000..e526520 --- /dev/null +++ b/libexec/container/rebuild @@ -0,0 +1,152 @@ +#!/bin/sh + +# Copyright (C) 2014-2022 Daniel Baumann <daniel.baumann@open-infrastructure.net> +# +# SPDX-License-Identifier: GPL-3.0+ +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. + +set -e + +PROJECT="open-infrastructure" +SOFTWARE="compute-tools" +PROGRAM="container" +COMMAND="$(basename ${0})" + +HOOKS="/etc/${SOFTWARE}/hooks" +MACHINES="/var/lib/machines" + +Parameters () +{ + OPTIONS_ALL="" + + GETOPT_LONGOPTIONS="name:,force,verbose," + GETOPT_OPTIONS="n:,f,v," + + PARAMETERS="$(getopt --longoptions ${GETOPT_LONGOPTIONS} --name=${COMMAND} --options ${GETOPT_OPTIONS} --shell sh -- ${@})" + + if [ "${?}" != "0" ] + then + echo "'${COMMAND}': getopt exit" >&2 + exit 1 + fi + + eval set -- "${PARAMETERS}" + + while true + do + case "${1}" in + -n|--name) + NAME="${2}" + shift 2 + ;; + + -f|--force) + FORCE="true" + shift 1 + + OPTIONS_ALL="${OPTIONS_ALL} --force" + ;; + + -v|--verbose) + VERBOSE="true" + shift 1 + + OPTIONS_ALL="${OPTIONS_ALL} --verbose" + ;; + + --) + shift 1 + break + ;; + + *) + echo "'${COMMAND}': getopt error" >&2 + exit 1 + ;; + esac + done +} + +Usage () +{ + echo "Usage: ${PROGRAM} ${COMMAND} -n|--name NAME [-f|--force] [-v|--verbose]" >&2 + echo + echo "See ${COMMAND}(1), ${PROGRAM}(1) and ${PROJECT}(7) for more information." + + exit 1 +} + +Parameters "${@}" + +if [ -z "${NAME}" ] +then + Usage +fi + +case "${NAME}" in + ALL) + NAMES="$(${PROGRAM} list --format shell --started)" + + for NAME in ${NAMES} + do + ${PROGRAM} rebuild,start --name ${NAME} ${OPTIONS_ALL} || true + done + + exit 0 + ;; +esac + +if [ ! -e "${MACHINES}/${NAME}" ] +then + echo "'${NAME}': no such container" >&2 + exit 1 +fi + +# Pre hooks +for FILE in "${HOOKS}/pre-${COMMAND}".* "${HOOKS}/${NAME}.pre-${COMMAND}" +do + if [ -x "${FILE}" ] + then + "${FILE}" + fi +done + +# Run +case "${VERBOSE}" in + true) + echo -n "Rebuilding container ${NAME}..." + ;; +esac + +${PROGRAM} stop ${OPTIONS_ALL} --name ${NAME} || true +sleep 0.5 +${PROGRAM} remove ${OPTIONS_ALL} --name ${NAME} || true +sleep 0.5 +${PROGRAM} build --name ${NAME} || true + +case "${VERBOSE}" in + true) + echo " done." + ;; +esac + +# Post hooks +for FILE in "${HOOKS}/post-${COMMAND}".* "${HOOKS}/${NAME}.post-${COMMAND}" +do + if [ -x "${FILE}" ] + then + "${FILE}" + fi +done diff --git a/libexec/container/remove b/libexec/container/remove new file mode 100755 index 0000000..4cb5d48 --- /dev/null +++ b/libexec/container/remove @@ -0,0 +1,261 @@ +#!/bin/sh + +# Copyright (C) 2014-2022 Daniel Baumann <daniel.baumann@open-infrastructure.net> +# +# SPDX-License-Identifier: GPL-3.0+ +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. + +set -e + +PROJECT="open-infrastructure" +SOFTWARE="compute-tools" +PROGRAM="container" +COMMAND="$(basename ${0})" + +CONFIG="/etc/${SOFTWARE}/config" +HOOKS="/etc/${SOFTWARE}/hooks" +MACHINES="/var/lib/machines" + +Parameters () +{ + OPTIONS_ALL="" + + GETOPT_LONGOPTIONS="name:,allow-stop,force,verbose," + GETOPT_OPTIONS="n:,f,v," + + PARAMETERS="$(getopt --longoptions ${GETOPT_LONGOPTIONS} --name=${COMMAND} --options ${GETOPT_OPTIONS} --shell sh -- ${@})" + + if [ "${?}" != "0" ] + then + echo "'${COMMAND}': getopt exit" >&2 + exit 1 + fi + + eval set -- "${PARAMETERS}" + + while true + do + case "${1}" in + -n|--name) + NAME="${2}" + shift 2 + ;; + + --allow-stop) + ALLOW_STOP="true" + shift 1 + + OPTIONS_ALL="${OPTIONS_ALL} --allow-stop" + ;; + + -f|--force) + FORCE="true" + shift 1 + + OPTIONS_ALL="${OPTIONS_ALL} --force" + ;; + + -v|--verbose) + VERBOSE="true" + shift 1 + + OPTIONS_ALL="${OPTIONS_ALL} --verbose" + ;; + + --) + shift 1 + break + ;; + + *) + echo "'${COMMAND}': getopt error" >&2 + exit 1 + ;; + esac + done +} + +Usage () +{ + echo "Usage: ${PROGRAM} ${COMMAND} -n|--name NAME [--allow-stop] [-f|--force] [-v|--verbose]" >&2 + echo + echo "See ${COMMAND}(1), ${PROGRAM}(1) and ${PROJECT}(7) for more information." + + exit 1 +} + +Rmdir () +{ + DIRECTORIES="${@}" + + for DIRECTORY in ${DIRECTORIES} + do + PARENT_DIRECTORY="/$(echo ${DIRECTORY} | cut -d / -f 2)" + + if [ "${PARENT_DIRECTORY}" != "${DIRECTORY}" ] + then + # the directory is at least two levels down from / + cd "${PARENT_DIRECTORY}" + + CRUFT="$(echo ${DIRECTORY} | cut -d / -f 3-)" + rmdir --ignore-fail-on-non-empty --parents "${CRUFT}" > /dev/null 2>&1 || true + + cd "${OLDPWD}" + fi + done +} + +Parameters "${@}" + +if [ -z "${NAME}" ] +then + Usage +fi + +case "${NAME}" in + ALL) + NAMES="$(${PROGRAM} list --format shell --stopped)" + + for NAME in ${NAMES} + do + ${PROGRAM} remove --name ${NAME} ${OPTIONS_ALL} || true + done + + exit 0 + ;; +esac + +if [ ! -e "${MACHINES}/${NAME}" ] && [ ! -e "${CONFIG}/${NAME}.conf" ] +then + echo "'${NAME}': no such container" >&2 + exit 1 +fi + +STATE="$(machinectl show ${NAME} 2>&1 | awk -FState= '/^State=/ { print $2 }')" + +case "${STATE}" in + running) + case "${ALLOW_STOP}" in + true) + echo "'${NAME}': container is started, stopping it now" >&2 + ${PROGRAM} stop -n ${NAME} -f + ;; + + *) + echo "'${NAME}': container is started" >&2 + exit 1 + ;; + esac + ;; +esac + +case "${FORCE}" in + true) + ;; + + *) + echo -n "'${NAME}': remove container '${NAME}' [y|N]? " + read FORCE + + FORCE="$(echo ${FORCE} | tr '[A-Z]' '[a-z]')" + + case "${FORCE}" in + y|yes) + ;; + + *) + exit 1 + ;; + esac + ;; +esac + +case "${VERBOSE}" in + true) + RM_OPTIONS="--verbose" + ;; + + *) + RM_OPTIONS="" + ;; +esac + +# Pre hooks +for FILE in "${HOOKS}/pre-${COMMAND}".* "${HOOKS}/${NAME}.pre-${COMMAND}" +do + if [ -x "${FILE}" ] + then + "${FILE}" + fi +done + +# data +if [ -e "${CONFIG}/${NAME}.conf" ] +then + # Removing rw bind mounts + BIND="$(awk -Fbind= '/^bind=/ { print $2 }' ${CONFIG}/${NAME}.conf)" + + if [ -n "${BIND}" ] + then + BINDS="$(echo ${BIND} | sed -e 's|;| |g')" + + for BIND in ${BINDS} + do + DIRECTORY="$(echo ${BIND} | awk -F: '{ print $1 }')" + + Rmdir "${DIRECTORY}" + done + fi + + # Removing ro bind mounts + BIND_RO="$(awk -Fbind-ro= '/^bind-ro=/ { print $2 }' ${CONFIG}/${NAME}.conf)" + + if [ -n "${BIND_RO}" ] + then + BINDS_RO="$(echo ${BIND_RO} | sed -e 's|;| |g')" + + for BIND_RO in ${BINDS_RO} + do + DIRECTORY="$(echo ${BIND_RO} | awk -F: '{ print $1 }')" + + Rmdir "${DIRECTORY}" + done + fi +fi + +# Run +case "${VERBOSE}" in + true) + echo -n "Removing container ${NAME}..." + ;; +esac + +rm --preserve-root --one-file-system -rf ${RM_OPTIONS} "${MACHINES}/${NAME}" +rm -f ${RM_OPTIONS} "${CONFIG}/${NAME}.conf" + +case "${VERBOSE}" in + true) + echo " done." + ;; +esac + +# Post hooks +for FILE in "${HOOKS}/post-${COMMAND}".* "${HOOKS}/${NAME}.post-${COMMAND}" +do + if [ -x "${FILE}" ] + then + "${FILE}" + fi +done diff --git a/libexec/container/restart b/libexec/container/restart new file mode 100755 index 0000000..0eb753c --- /dev/null +++ b/libexec/container/restart @@ -0,0 +1,172 @@ +#!/bin/sh + +# Copyright (C) 2014-2022 Daniel Baumann <daniel.baumann@open-infrastructure.net> +# +# SPDX-License-Identifier: GPL-3.0+ +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. + +set -e + +PROJECT="open-infrastructure" +SOFTWARE="compute-tools" +PROGRAM="container" +COMMAND="$(basename ${0})" + +HOOKS="/etc/${SOFTWARE}/hooks" +MACHINES="/var/lib/machines" + +Parameters () +{ + OPTIONS_ALL="" + + GETOPT_LONGOPTIONS="name:,force,interactive,verbose," + GETOPT_OPTIONS="n:,f,i,v," + + PARAMETERS="$(getopt --longoptions ${GETOPT_LONGOPTIONS} --name=${COMMAND} --options ${GETOPT_OPTIONS} --shell sh -- ${@})" + + if [ "${?}" != "0" ] + then + echo "'${COMMAND}': getopt exit" >&2 + exit 1 + fi + + eval set -- "${PARAMETERS}" + + while true + do + case "${1}" in + -n|--name) + NAME="${2}" + shift 2 + ;; + + -f|--force) + FORCE="true" + shift 1 + + OPTIONS_ALL="${OPTIONS_ALL} --force" + ;; + + -i|--interactive) + INTERACTIVE="true" + shift 1 + + OPTIONS_ALL="${OPTIONS_ALL} --interactive" + ;; + + -v|--verbose) + VERBOSE="true" + shift 1 + + OPTIONS_ALL="${OPTIONS_ALL} --verbose" + ;; + + --) + shift 1 + break + ;; + + *) + echo "'${COMMAND}': getopt error" >&2 + exit 1 + ;; + esac + done +} + +Usage () +{ + echo "Usage: ${PROGRAM} ${COMMAND} -n|--name NAME [-f|--force] [-i|--interactive] [-v|--verbose]" >&2 + echo + echo "See ${COMMAND}(1), ${PROGRAM}(1) and ${PROJECT}(7) for more information." + + exit 1 +} + +Parameters "${@}" + +if [ -z "${NAME}" ] +then + Usage +fi + +case "${NAME}" in + ALL) + NAMES="$(${PROGRAM} list --format shell --started)" + + for NAME in ${NAMES} + do + ${PROGRAM} restart --name ${NAME} ${OPTIONS_ALL} || true + done + + exit 0 + ;; +esac + +if [ ! -e "${MACHINES}/${NAME}" ] +then + echo "'${NAME}': no such container" >&2 + exit 1 +fi + +if [ "${FORCE}" != "true" ] || [ "${INTERACTIVE}" = "true" ] +then + echo -n "'${NAME}': restart container '${NAME}' [y|N]? " + read STOP + + STOP="$(echo ${STOP} | tr '[A-Z]' '[a-z]')" + + case "${STOP}" in + y|yes) + ;; + + *) + exit 1 + ;; + esac +fi + +# Pre hooks +for FILE in "${HOOKS}/pre-${COMMAND}".* "${HOOKS}/${NAME}.pre-${COMMAND}" +do + if [ -x "${FILE}" ] + then + "${FILE}" + fi +done + +# Run +case "${VERBOSE}" in + true) + echo -n "Restarting container ${NAME}..." + ;; +esac + +machinectl reboot ${NAME} + +case "${VERBOSE}" in + true) + echo " done." + ;; +esac + +# Post hooks +for FILE in "${HOOKS}/post-${COMMAND}".* "${HOOKS}/${NAME}.post-${COMMAND}" +do + if [ -x "${FILE}" ] + then + "${FILE}" + fi +done diff --git a/libexec/container/run b/libexec/container/run new file mode 100755 index 0000000..4daeaa2 --- /dev/null +++ b/libexec/container/run @@ -0,0 +1,131 @@ +#!/bin/sh + +# Copyright (C) 2014-2019 Daniel Baumann <daniel.baumann@open-infrastructure.net> +# +# SPDX-License-Identifier: GPL-3.0+ +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. + +set -e + +PROJECT="open-infrastructure" +SOFTWARE="compute-tools" +PROGRAM="container" +COMMAND="$(basename ${0})" + +HOOKS="/etc/${SOFTWARE}/hooks" +MACHINES="/var/lib/machines" + +Parameters () +{ + GETOPT_LONGOPTIONS="name:," + GETOPT_OPTIONS="n:," + + PARAMETERS="$(getopt --longoptions ${GETOPT_LONGOPTIONS} --name=${COMMAND} --options ${GETOPT_OPTIONS} --shell sh -- ${@})" + + if [ "${?}" != "0" ] + then + echo "'${COMMAND}': getopt exit" >&2 + exit 1 + fi + + eval set -- "${PARAMETERS}" + + while true + do + case "${1}" in + -n|--name) + NAME="${2}" + shift 2 + ;; + + --) + shift 1 + break + ;; + + *) + echo "'${COMMAND}': getopt error" >&2 + exit 1 + ;; + esac + done +} + +Usage () +{ + echo "Usage: ${PROGRAM} ${COMMAND} -n|--name NAME -- COMMAND" >&2 + echo + echo "See ${COMMAND}(1), ${PROGRAM}(1) and ${PROJECT}(7) for more information." + + exit 1 +} + +Parameters "${@}" + +if [ -z "${NAME}" ] +then + Usage +fi + +if [ ! -e "${MACHINES}/${NAME}" ] +then + echo "'${NAME}': no such container" >&2 + exit 1 +fi + +STATE="$(machinectl show ${NAME} 2>&1 | awk -F= '/^State=/ { print $2 }')" + +case "${STATE}" in + running) + ;; + + *) + echo "'${NAME}': container is not running" >&2 + exit 1 + ;; +esac + +LEADER="$(machinectl status ${NAME} | awk '/Leader: / { print $2 }')" + +# Pre hooks +for FILE in "${HOOKS}/pre-${COMMAND}".* "${HOOKS}/${NAME}.pre-${COMMAND}" +do + if [ -x "${FILE}" ] + then + "${FILE}" + fi +done + +SSH_CLIENT="${SSH_CLIENT:-127.0.0.1 0 0}" + +RUN="$(echo ${@} | awk -F-- '{ print $2}')" + +# Run +nsenter --all --target "${LEADER}" --wd="${MACHINES}/${NAME}/root" /usr/bin/script -c "LC_ALL=C.UTF-8 /bin/bash -l -c \"${RUN}\"" -q /dev/null + +case "${SSH_CLIENT}" in + 127.0.0.1*) + unset SSH_CLIENT + ;; +esac + +# Post hooks +for FILE in "${HOOKS}/post-${COMMAND}".* "${HOOKS}/${NAME}.post-${COMMAND}" +do + if [ -x "${FILE}" ] + then + "${FILE}" + fi +done diff --git a/libexec/container/start b/libexec/container/start new file mode 100755 index 0000000..1f22325 --- /dev/null +++ b/libexec/container/start @@ -0,0 +1,570 @@ +#!/bin/sh + +# Copyright (C) 2014-2022 Daniel Baumann <daniel.baumann@open-infrastructure.net> +# +# SPDX-License-Identifier: GPL-3.0+ +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. + +set -e + +PROJECT="open-infrastructure" +SOFTWARE="compute-tools" +PROGRAM="container" +COMMAND="$(basename ${0})" + +CONFIG="/etc/${SOFTWARE}/config" +HOOKS="/etc/${SOFTWARE}/hooks" +MACHINES="/var/lib/machines" + +START="false" +SYSTEMCTL="true" + +Parameters () +{ + OPTIONS_ALL="" + + GETOPT_LONGOPTIONS="name:,force,nspawn,start,verbose," + GETOPT_OPTIONS="n:,f,v," + + PARAMETERS="$(getopt --longoptions ${GETOPT_LONGOPTIONS} --name=${COMMAND} --options ${GETOPT_OPTIONS} --shell sh -- ${@})" + + if [ "${?}" != "0" ] + then + echo "'${COMMAND}': getopt exit" >&2 + exit 1 + fi + + eval set -- "${PARAMETERS}" + + while true + do + case "${1}" in + -n|--name) + NAME="${2}" + shift 2 + ;; + + -f|--force) + FORCE="true" + shift 1 + + OPTIONS_ALL="${OPTIONS_ALL} --force" + ;; + + --nspawn) + # internal option + SYSTEMCTL="false" + shift 1 + ;; + + --start) + # internal option + START="true" + SYSTEMCTL="false" + shift 1 + ;; + + -v|--verbose) + VERBOSE="true" + shift 1 + + OPTIONS_ALL="${OPTIONS_ALL} --verbose" + ;; + + --) + shift 1 + break + ;; + + *) + echo "'${COMMAND}': getopt error" >&2 + exit 1 + ;; + esac + done +} + +Usage () +{ + echo "Usage: ${PROGRAM} ${COMMAND} -n|--name NAME [-f|--force]" >&2 + echo + echo "See ${COMMAND}(1), ${PROGRAM}(1) and ${PROJECT}(7) for more information." + + exit 1 +} + +Parameters "${@}" + +if [ -z "${NAME}" ] +then + Usage +fi + +# hooks +export NAME + +case "${NAME}" in + ALL) + NAMES="$(${PROGRAM} list --format shell --stopped)" + + for NAME in ${NAMES} + do + ${PROGRAM} start --name ${NAME} ${OPTIONS_ALL} || true + done + + exit 0 + ;; +esac + +if [ ! -e "${MACHINES}/${NAME}" ] +then + echo "'${NAME}': no such container" >&2 + exit 1 +fi + +# options +if grep -Eqs "^ *cnt.start=" "${CONFIG}/${NAME}.conf" | grep -qs force +then + FORCE="true" +fi + +case "${START}" in + false) + STATE="$(machinectl show ${NAME} 2>&1 | awk -FState= '/^State=/ { print $2 }')" + + case "${STATE}" in + running) + echo "'${NAME}': container is already started" >&2 + exit 1 + ;; + esac + ;; +esac + +if [ -e "${MACHINES}/.#${NAME}.lck" ] +then + case "${FORCE}" in + true) + rm -f "${MACHINES}/.#${NAME}.lck" + + VETHS="$(awk -Fnetwork-veth-extra= '/^network-veth-extra=/ { print $2 }' ${CONFIG}/${NAME}.conf | awk -F: '{ print $1 }')" + + for VETH in ${VETHS} + do + ip link delete ${VETH} > /dev/null 2>&1 || true + done + ;; + + *) + echo "'${NAME}': container is locked" >&2 + exit 1 + ;; + esac +fi + +HOST_ARCHITECTURE="$(dpkg --print-architecture)" +MACHINE_ARCHITECTURE="$(chroot ${MACHINES}/${NAME} dpkg --print-architecture)" + +case "${HOST_ARCHITECTURE}" in + amd64) + case "${MACHINE_ARCHITECTURE}" in + i386) + SETARCH="setarch i686" + ;; + + *) + SETARCH="" + ;; + esac + ;; +esac + +if systemctl status systemd-networkd > /dev/null 2>&1 +then + NETWORK_SUBSYSTEM="systemd-networkd" +else + NETWORK_SUBSYSTEM="ifupdown" +fi + +case "${START}" in + start) + ;; + + *) + # Pre hooks + for FILE in "${HOOKS}/pre-${COMMAND}".* "${HOOKS}/${NAME}.pre-${COMMAND}" + do + if [ -x "${FILE}" ] + then + "${FILE}" + fi + done + ;; +esac + +# config +if [ -e "${CONFIG}/${NAME}.conf" ] +then + CNT_OVERLAY="$(awk -Fcnt.overlay= '/^cnt.overlay=/ { print $2 }' ${CONFIG}/${NAME}.conf)" + CNT_OVERLAY_OPTIONS="$(awk -Fcnt.overlay-options= '/^cnt.overlay-options=/ { print $2 }' ${CONFIG}/${NAME}.conf)" + + if [ -n "${CNT_OVERLAY}" ] + then + CNT_OVERLAYS="$(echo ${CNT_OVERLAY} | sed -e 's|;| |g')" + + COUNT="0" + for CNT_OVERLAY in ${CNT_OVERLAYS} + do + DIRECTORY_LOWER="$(echo ${CNT_OVERLAY} | awk -F: '{ print $1 }')" + DIRECTORY_UPPER="$(echo ${CNT_OVERLAY} | awk -F: '{ print $2 }')" + DIRECTORY_WORK="$(echo ${CNT_OVERLAY} | awk -F: '{ print $3 }')" + DIRECTORY_MERGED="$(echo ${CNT_OVERLAY} | awk -F: '{ print $4 }')" + + COUNT="$((${COUNT} + 1))" + CNT_OVERLAY_OPTION="$(echo ${CNT_OVERLAY_OPTIONS} | awk -F ';' "{ print \$${COUNT} }")" + + for DIRECTORY in "${DIRECTORY_LOWER}" "${DIRECTORY_UPPER}" "${DIRECTORY_WORK}" "${DIRECTORY_MERGED}" + do + mkdir -p "${DIRECTORY}" + done + + if ! findmnt -n -o SOURCE "${DIRECTORY_MERGED}" | grep -qs '^cnt.overlay-' + then + if [ -n "${CNT_OVERLAY_OPTION}" ] + then + CNT_OVERLAY_OPTION="-o ${CNT_OVERLAY_OPTION}" + fi + + mount cnt.overlay-${NAME} -t overlay ${CNT_OVERLAY_OPTION} -olowerdir="${DIRECTORY_LOWER}",upperdir="${DIRECTORY_UPPER}",workdir="${DIRECTORY_WORK}",default_permissions "${DIRECTORY_MERGED}" + fi + done + fi + + BIND="$(awk -Fbind= '/^bind=/ { print $2 }' ${CONFIG}/${NAME}.conf)" + + if [ -n "${BIND}" ] + then + BINDS="$(echo ${BIND} | sed -e 's|;| |g')" + + for BIND in ${BINDS} + do + DIRECTORY="$(echo ${BIND} | awk -F: '{ print $1 }')" + + if [ ! -e "${DIRECTORY}" ] + then + echo "'${DIRECTORY}': creating non-existing directory for bind mounting" + mkdir -p "${DIRECTORY}" + fi + done + + BIND="" + + for DIRECTORIES in ${BINDS} + do + BIND="${BIND} --bind ${DIRECTORIES}" + done + fi + + BIND_RO="$(awk -Fbind-ro= '/^bind-ro=/ { print $2 }' ${CONFIG}/${NAME}.conf)" + + if [ -n "${BIND_RO}" ] + then + BINDS_RO="$(echo ${BIND_RO} | sed -e 's|;| |g')" + + for BIND_RO in ${BINDS_RO} + do + DIRECTORY="$(echo ${BIND_RO} | awk -F: '{ print $1 }')" + + if [ ! -e "${DIRECTORY}" ] + then + echo "'${DIRECTORY}': creating non-existing directory for bind-ro mounting" + mkdir -p "${DIRECTORY}" + fi + done + + BIND_RO="" + + for DIRECTORIES in ${BINDS_RO} + do + BIND_RO="${BIND_RO} --bind-ro ${DIRECTORIES}" + done + fi + + BOOT="$(awk -Fboot= '/^boot=/ { print $2 }' ${CONFIG}/${NAME}.conf || echo yes)" + + case "${BOOT}" in + yes) + BOOT="--boot" + ;; + + *) + BOOT="" + ;; + esac + + CAPABILITY="$(awk -Fcapability= '/^capability=/ { print $2 }' ${CONFIG}/${NAME}.conf)" + + case "${CAPABILITY}" in + "") + CAPABILITY="" + ;; + + *) + CAPABILITY="--capability=${CAPABILITY}" + ;; + esac + + DIRECTORY="$(awk -Fdirectory= '/^directory=/ { print $2 }' ${CONFIG}/${NAME}.conf || echo ${MACHINES}/${NAMES})" + DIRECTORY="--directory ${DIRECTORY}" + + DROP_CAPABILITY="$(awk -Fdrop-capability= '/^drop-capability=/ { print $2 }' ${CONFIG}/${NAME}.conf)" + + case "${DROP_CAPABILITY}" in + "") + DROP_CAPABILITY="" + ;; + + *) + DROP_CAPABILITY="--drop-capability=${DROP_CAPABILITY}" + ;; + esac + + LINK_JOURNAL="$(awk -Flink-journal= '/^link-journal=/ { print $2 }' ${CONFIG}/${NAME}.conf || echo no)" + + case "${LINK_JOURNAL}" in + yes) + LINK_JOURNAL="--link-journal=yes" + ;; + + *) + LINK_JOURNAL="--link-journal=no" + ;; + esac + + MACHINE="--machine=${NAME}" + + NETWORK_VETH_EXTRA="" + + VETHS="$(awk -Fnetwork-veth-extra= '/^network-veth-extra=/ { print $2 }' ${CONFIG}/${NAME}.conf)" + + case "${VETHS}" in + "") + ;; + + *) + for VETH in ${VETHS} + do + NETWORK_VETH_EXTRA="${NETWORK_VETH_EXTRA} --network-veth-extra=${VETH}" + INTERFACE="$(echo ${VETH} | awk -F: '{ print $1 }')" + + if [ "$(echo ${INTERFACE} | wc -c)" -gt 16 ] + then + echo "'${INTERFACE}': name exceeds maximum of 15 characters, network might be not working." + fi + done + ;; + esac + + NETWORK_BRIDGES="$(awk -Fcnt.network-bridge= '/^cnt.network-bridge=/ { print $2 }' ${CONFIG}/${NAME}.conf)" + + case "${NETWORK_BRIDGES}" in + "") + ;; + + *) + for BRIDGE_DEFINITION in ${NETWORK_BRIDGES} + do + INTERFACE="$(echo ${BRIDGE_DEFINITION} | awk -F: '{ print $1 }')" + BRIDGE="$(echo ${BRIDGE_DEFINITION} | awk -F: '{ print $2 }')" + + if [ "$(echo ${INTERFACE} | wc -c)" -gt 16 ] + then + echo "'${INTERFACE}': name exceeds maximum of 15 characters, network might be not working." + fi + + if [ -n "${BRIDGE}" ] && [ -n "${INTERFACE}" ] + then + + case "${NETWORK_SUBSYSTEM}" in + ifupdown) + +cat > "/etc/network/interfaces.d/${INTERFACE}" << EOF +allow-hotplug ${INTERFACE} +iface ${INTERFACE} inet manual + pre-up ip link set ${INTERFACE} up + post-up ip link set ${INTERFACE} master ${BRIDGE} + pre-down ip link set ${INTERFACE} nomaster + post-down ip link set ${INTERFACE} down +EOF + + ;; + + systemd-networkd) + mkdir -p /run/systemd/network + +cat > "/run/systemd/network/${INTERFACE}.network" << EOF +[Match] +Name=${INTERFACE} + +[Network] +Bridge=${BRIDGE} +EOF + + networkctl reload + ;; + esac + else + echo "Warning bridge definition '${BRIDGE_DEFINITION}' not recognized (expected <bridge>:<interface>): Ignoring" + fi + done + ;; + esac + + PRIVATE_USERS="$(awk -Fprivate-users= '/^private-users=/ { print $2 }' ${CONFIG}/${NAME}.conf || echo no)" + + case "${PRIVATE_USERS}" in + yes) + PRIVATE_USERS="--private-users=yes" + ;; + + *) + PRIVATE_USERS="--private-users=no" + ;; + esac + + REGISTER="$(awk -Fregister= '/^register=/ { print $2 }' ${CONFIG}/${NAME}.conf || echo yes)" + + case "${REGISTER}" in + yes) + REGISTER="--register=yes" + ;; + + *) + REGISTER="--register=no" + ;; + esac + + BLOCK_IO_DEVICE_WEIGHT="$(awk -FBlockIODeviceWeight= '/^BlockIODeviceWeight=/ { print $2 }' ${CONFIG}/${NAME}.conf)" + + if [ -n "${BLOCK_IO_DEVICE_WEIGHT}" ] + then + BLOCK_IO_DEVICE_WEIGHT="BlockIODeviceWeight=${BLOCK_IO_DEVICE_WEIGHT}" + SET_PROPERTY="true" + fi + + BLOCK_IO_READ_BANDWIDTH="$(awk -FBlockIOReadBandwidth= '/^BlockIOReadBandwidth=/ { print $2 }' ${CONFIG}/${NAME}.conf)" + + if [ -n "${BLOCK_IO_READ_BANDWIDTH}" ] + then + BLOCK_IO_READ_BANDWIDTH="BlockIOReadBandwidth=${BLOCK_IO_READ_BANDWIDTH}" + SET_PROPERTY="true" + fi + + BLOCK_IO_WEIGHT="$(awk -FBlockIOWeight= '/^BlockIOWeight=/ { print $2 }' ${CONFIG}/${NAME}.conf)" + + if [ -n "${BLOCK_IO_WEIGHT}" ] + then + BLOCK_IO_WEIGHT="BlockIOWeight=${BLOCK_IO_WEIGHT}" + SET_PROPERTY="true" + fi + + BLOCK_IO_WRITE_BANDWIDTH="$(awk -FBlockIOWriteBandwidth=/= '/^BlockIOWriteBandwidth=/ { print $2 }' ${CONFIG}/${NAME}.conf)" + + if [ -n "${BLOCK_IO_WRITE_BANDWIDTH}" ] + then + BLOCK_IO_WRITE_BANDWIDTH="BlockIOWriteBandwidth=${BLOCK_IO_WRITE_BANDWIDTH}" + SET_PROPERTY="true" + fi + + CPU_QUOTA="$(awk -FCPUQuota= '/^CPUQuota=/ { print $2 }' ${CONFIG}/${NAME}.conf)" + + if [ -n "${CPU_QUOTA}" ] + then + CPU_QUOTA="CPUQuota=${CPU_QUOTA}" + SET_PROPERTY="true" + fi + + CPU_SHARES="$(awk -FCPUShares= '/^CPUShares=/ { print $2 }' ${CONFIG}/${NAME}.conf)" + + if [ -n "${CPU_SHARES}" ] + then + CPU_SHARES="CPUShares=${CPU_SHARES}" + SET_PROPERTY="true" + fi + + MEMORY_LIMIT="$(awk -FMemoryLimit= '/^MemoryLimit=/ { print $2 }' ${CONFIG}/${NAME}.conf)" + + if [ -n "${MEMORY_LIMIT}" ] + then + MEMORY_LIMIT="MemoryLimit=${MEMORY_LIMIT}" + SET_PROPERTY="true" + fi + + TASKS_MAX="$(awk -FTasksMax= '/^TasksMax=/ { print $2 }' ${CONFIG}/${NAME}.conf)" + + if [ -n "${TASKS_MAX}" ] + then + TASKS_MAX="TasksMax=${TASKS_MAX}" + SET_PROPERTY="true" + fi +fi + +case "${SYSTEMCTL}" in + true) + systemctl start ${PROGRAM}@${NAME}.service + + # Post hooks + for FILE in "${HOOKS}/post-${COMMAND}".* "${HOOKS}/${NAME}.post-${COMMAND}" + do + if [ -x "${FILE}" ] + then + "${FILE}" + fi + done + + exit 0 + ;; +esac + +case "${START}" in + true) + case "${SET_PROPERTY}" in + true) + systemctl --runtime set-property ${NAME} ${BLOCK_IO_DEVICE_WEIGHT} ${BLOCK_IO_READ_BANDWIDTH} ${BLOCK_IO_WEIGHT} ${BLOCK_IO_WRITE_BANDWIDTH} ${CPU_QUOTA} ${CPU_SHARES} ${MEMORY_LIMIT} ${TASKS_MAX} + ;; + esac + ;; + + *) + # Run + + case "${VERBOSE}" in + true) + echo -n "Starting container ${NAME}..." + ;; + esac + + mkdir -p "/var/lib/${SOFTWARE}/state" + echo "start" > "/var/lib/${SOFTWARE}/state/${NAME}.run" + + ${SETARCH} systemd-nspawn --keep-unit ${BIND} ${BIND_RO} ${BOOT} ${CAPABILITY} ${DIRECTORY} ${DROP_CAPABILITY} ${MACHINE} ${NETWORK_VETH_EXTRA} ${LINK_JOURNAL} ${REGISTER} + + case "${VERBOSE}" in + true) + echo " done." + ;; + esac + ;; +esac diff --git a/libexec/container/status b/libexec/container/status new file mode 100755 index 0000000..5b930a7 --- /dev/null +++ b/libexec/container/status @@ -0,0 +1,108 @@ +#!/bin/sh + +# Copyright (C) 2016 Simon Spöehel <simon.spoehel@open-infrastructure.net> +# +# SPDX-License-Identifier: GPL-3.0+ +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. + +set -e + +PROJECT="open-infrastructure" +SOFTWARE="compute-tools" +PROGRAM="container" +COMMAND="$(basename ${0})" + +CONFIG="/etc/${SOFTWARE}/config" +HOOKS="/etc/${SOFTWARE}/hooks" +MACHINES="/var/lib/machines" + +Parameters () +{ + GETOPT_LONGOPTIONS="name:," + GETOPT_OPTIONS="n:," + + PARAMETERS="$(getopt --longoptions ${GETOPT_LONGOPTIONS} --name=${COMMAND} --options ${GETOPT_OPTIONS} --shell sh -- ${@})" + + if [ "${?}" != "0" ] + then + echo "'${COMMAND}': getopt exit" >&2 + exit 1 + fi + + eval set -- "${PARAMETERS}" + + while true + do + case "${1}" in + -n|--name) + NAME="${2}" + shift 2 + ;; + + --) + shift 1 + break + ;; + + *) + echo "'${COMMAND}': getopt error" >&2 + exit 1 + ;; + esac + done +} + +Usage () +{ + echo "Usage: ${PROGRAM} ${COMMAND} -n|--name NAME" >&2 + echo + echo "See ${COMMAND}(1), ${PROGRAM}(1) and ${PROJECT}(7) for more information." + + exit 1 +} + +Parameters "${@}" + +if [ -z "${NAME}" ] +then + Usage +fi + +if [ ! -e "${MACHINES}/${NAME}" ] +then + echo "'${NAME}': no such container" >&2 + exit 1 +fi + +# Pre hooks +for FILE in "${HOOKS}/pre-${COMMAND}".* "${HOOKS}/${NAME}.pre-${COMMAND}" +do + if [ -x "${FILE}" ] + then + "${FILE}" + fi +done + +# Run +systemctl status ${PROGRAM}@${NAME}.service --full + +# Post hooks +for FILE in "${HOOKS}/post-${COMMAND}".* "${HOOKS}/${NAME}.post-${COMMAND}" +do + if [ -x "${FILE}" ] + then + "${FILE}" + fi +done diff --git a/libexec/container/stop b/libexec/container/stop new file mode 100755 index 0000000..8ca98ce --- /dev/null +++ b/libexec/container/stop @@ -0,0 +1,360 @@ +#!/bin/sh + +# Copyright (C) 2014-2022 Daniel Baumann <daniel.baumann@open-infrastructure.net> +# +# SPDX-License-Identifier: GPL-3.0+ +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. + +set -e + +PROJECT="open-infrastructure" +SOFTWARE="compute-tools" +PROGRAM="container" +COMMAND="$(basename ${0})" + +CONFIG="/etc/${SOFTWARE}/config" +HOOKS="/etc/${SOFTWARE}/hooks" +MACHINES="/var/lib/machines" + +CLEAN="false" + +Parameters () +{ + OPTIONS_ALL="" + + GETOPT_LONGOPTIONS="name:,force,interactive,kill,clean,stateless,verbose," + GETOPT_OPTIONS="n:,f,i,k,v," + + PARAMETERS="$(getopt --longoptions ${GETOPT_LONGOPTIONS} --name=${COMMAND} --options ${GETOPT_OPTIONS} --shell sh -- ${@})" + + if [ "${?}" != "0" ] + then + echo "'${COMMAND}': getopt exit" >&2 + exit 1 + fi + + eval set -- "${PARAMETERS}" + + while true + do + case "${1}" in + -n|--name) + NAME="${2}" + shift 2 + ;; + + -f|--force) + FORCE="true" + shift 1 + + OPTIONS_ALL="${OPTIONS_ALL} --force" + ;; + + -i|--interactive) + INTERACTIVE="true" + shift 1 + + OPTIONS_ALL="${OPTIONS_ALL} --interactive" + ;; + + -k|--kill) + KILL="true" + shift 1 + + OPTIONS_ALL="${OPTIONS_ALL} --kill" + ;; + + --clean) + # internal option + CLEAN="true" + shift 1 + + OPTONS_ALL="${OPTIONS_ALL} --clean" + ;; + + --stateless) + # internal option + STATELESS="true" + shift 1 + + OPTIONS_ALL="${OPTIONS_ALL} --stateless" + ;; + + -v|--verbose) + VERBOSE="true" + shift 1 + + OPTIONS_ALL="${OPTIONS_ALL} --verbose" + ;; + + --) + shift 1 + break + ;; + + *) + echo "'${COMMAND}': getopt error" >&2 + exit 1 + ;; + esac + done +} + +Usage () +{ + echo "Usage: ${PROGRAM} ${COMMAND} -n|--name NAME [-f|--force] [-i|--interactive] [-v|--verbose]" >&2 + echo + echo "See ${COMMAND}(1), ${PROGRAM}(1) and ${PROJECT}(7) for more information." + + exit 1 +} + +Rmdir () +{ + DIRECTORIES="${@}" + + for DIRECTORY in ${DIRECTORIES} + do + PARENT_DIRECTORY="/$(echo ${DIRECTORY} | cut -d / -f 2)" + + if [ "${PARENT_DIRECTORY}" != "${DIRECTORY}" ] + then + # the directory is at least two levels down from / + cd "${PARENT_DIRECTORY}" + + CRUFT="$(echo ${DIRECTORY} | cut -d / -f 3-)" + rmdir --ignore-fail-on-non-empty --parents "${CRUFT}" > /dev/null 2>&1 || true + + cd "${OLDPWD}" + fi + done +} + +Parameters "${@}" + +if [ -z "${NAME}" ] +then + Usage +fi + +case "${NAME}" in + ALL) + NAMES="$(${PROGRAM} list --format shell --started)" + + for NAME in ${NAMES} + do + ${PROGRAM} stop --name ${NAME} ${OPTIONS_ALL} || true + done + + exit 0 + ;; +esac + +if [ ! -e "${MACHINES}/${NAME}" ] +then + echo "'${NAME}': no such container" >&2 + exit 1 +fi + +if systemctl status systemd-networkd > /dev/null 2>&1 +then + NETWORK_SUBSYSTEM="systemd-networkd" +else + NETWORK_SUBSYSTEM="ifupdown" +fi + +# Pre hooks +for FILE in "${HOOKS}/pre-${COMMAND}".* "${HOOKS}/${NAME}.pre-${COMMAND}" +do + if [ -x "${FILE}" ] + then + "${FILE}" + fi +done + +STATE="$(machinectl show ${NAME} 2>&1 | awk -FState= '/^State=/ { print $2 }')" + +case "${CLEAN}" in + true) + # Removing overlay mounts + CNT_OVERLAY="$(awk -Fcnt.overlay= '/^cnt.overlay=/ { print $2 }' ${CONFIG}/${NAME}.conf)" + + if [ -n "${CNT_OVERLAY}" ] + then + CNT_OVERLAYS="$(echo ${CNT_OVERLAY} | sed -e 's|;| |g')" + + for CNT_OVERLAY in ${CNT_OVERLAYS} + do + DIRECTORY_LOWER="$(echo ${CNT_OVERLAY} | awk -F: '{ print $1 }')" + DIRECTORY_UPPER="$(echo ${CNT_OVERLAY} | awk -F: '{ print $2 }')" + DIRECTORY_WORK="$(echo ${CNT_OVERLAY} | awk -F: '{ print $3 }')" + DIRECTORY_MERGED="$(echo ${CNT_OVERLAY} | awk -F: '{ print $4 }')" + + umount -f "${DIRECTORY_MERGED}" + + Rmdir "${DIRECTORY_LOWER}" "${DIRECTORY_UPPER}" "${DIRECTORY_WORK}" "${DIRECTORY_MERGED}" + done + fi + + # Removing rw bind mounts + BIND="$(awk -Fbind= '/^bind=/ { print $2 }' ${CONFIG}/${NAME}.conf)" + + if [ -n "${BIND}" ] + then + BINDS="$(echo ${BIND} | sed -e 's|;| |g')" + + for BIND in ${BINDS} + do + DIRECTORY="$(echo ${BIND} | awk -F: '{ print $1 }')" + + Rmdir "${DIRECTORY}" + done + fi + + # Removing ro bind mounts + BIND_RO="$(awk -Fbind-ro= '/^bind-ro=/ { print $2 }' ${CONFIG}/${NAME}.conf)" + + if [ -n "${BIND_RO}" ] + then + BINDS_RO="$(echo ${BIND_RO} | sed -e 's|;| |g')" + + for BIND_RO in ${BINDS_RO} + do + DIRECTORY="$(echo ${BIND_RO} | awk -F: '{ print $1 }')" + + Rmdir "${DIRECTORY}" + done + fi + + # Removing network configuration + VETHS="$(awk -Fnetwork-veth-extra= '/^network-veth-extra=/ { print $2 }' ${CONFIG}/${NAME}.conf)" + + case "${VETHS}" in + "") + ;; + + *) + for VETH in ${VETHS} + do + INTERFACE="$(echo ${VETH} | awk -F: '{ print $1 }')" + + case "${NETWORK_SUBSYSTEM}" in + ifupdown) + FILE="/etc/network/interfaces.d/${INTERFACE}" + ;; + + systemd-networkd) + FILE="/run/systemd/network/${INTERFACE}.network" + ;; + esac + + if [ -f "${FILE}" ] + then + rm -f "${FILE}" + fi + done + ;; + esac + + exit 0 + ;; + + *) + ;; +esac + +case "${STATE}" in + running) + ;; + + *) + echo "'${NAME}': container is already stopped" >&2 + exit 1 + ;; +esac + +case "${KILL}" in + true) + MODE="terminate" + ;; + + *) + MODE="poweroff" + ;; +esac + +if [ "${FORCE}" != "true" ] || [ "${INTERACTIVE}" = "true" ] +then + echo -n "'${NAME}': stop container '${NAME}' [y|N]? " + read STOP + + STOP="$(echo ${STOP} | tr '[A-Z]' '[a-z]')" + + case "${STOP}" in + y|yes) + ;; + + *) + exit 1 + ;; + esac +fi + +# Run +case "${VERBOSE}" in + true) + echo -n "Stopping container ${NAME}..." + ;; +esac + +machinectl ${MODE} ${NAME} + +case "${KILL}" in + true) + VETHS="$(awk -Fnetwork-veth-extra= '/^network-veth-extra=/ { print $2 }' ${CONFIG}/${NAME}.conf | awk -F: '{ print $1 }')" + + for VETH in ${VETHS} + do + ip link delete ${VETH} > /dev/null 2>&1 || true + done + + rm -f "${MACHINES}/.#${NAME}.lck" + ;; +esac + +case "${STATELESS}" in + true) + ;; + + *) + mkdir -p "/var/lib/${SOFTWARE}/state" + echo "stop" > "/var/lib/${SOFTWARE}/state/${NAME}.run" + ;; +esac + +case "${VERBOSE}" in + true) + echo " done." + ;; +esac + +# Post hooks +for FILE in "${HOOKS}/post-${COMMAND}".* "${HOOKS}/${NAME}.post-${COMMAND}" +do + if [ -x "${FILE}" ] + then + "${FILE}" + fi +done diff --git a/libexec/container/top b/libexec/container/top new file mode 100755 index 0000000..268da9a --- /dev/null +++ b/libexec/container/top @@ -0,0 +1,122 @@ +#!/bin/sh + +# Copyright (C) 2014-2022 Daniel Baumann <daniel.baumann@open-infrastructure.net> +# +# SPDX-License-Identifier: GPL-3.0+ +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. + +set -e + +PROJECT="open-infrastructure" +SOFTWARE="compute-tools" +PROGRAM="container" +COMMAND="$(basename ${0})" + +Parameters () +{ + GETOPT_LONGOPTIONS="delay:," + GETOPT_OPTIONS="d:," + + PARAMETERS="$(getopt --longoptions ${GETOPT_LONGOPTIONS} --name=${COMMAND} --options ${GETOPT_OPTIONS} --shell sh -- ${@})" + + if [ "${?}" != "0" ] + then + echo "'${COMMAND}': getopt exit" >&2 + exit 1 + fi + + eval set -- "${PARAMETERS}" + + while true + do + case "${1}" in + -d|--delay) + DELAY="${2}" + shift 2 + ;; + + --) + shift 1 + break + ;; + + *) + echo "'${COMMAND}': getopt error" >&2 + exit 1 + ;; + esac + done +} + +Usage () +{ + echo "Usage: ${PROGRAM} ${COMMAND} [-d|--delay DELAY]" >&2 + echo + echo "See ${COMMAND}(1), ${PROGRAM}(1) and ${PROJECT}(7) for more information." + + exit 1 +} + +Parameters "${@}" + +DELAY="${DELAY:-1}" +HOST="$(hostname -f 2> /dev/null || hostname)" + +# Pre hooks +for FILE in "${HOOKS}/pre-${COMMAND}".* "${HOOKS}/${NAME}.pre-${COMMAND}" +do + if [ -x "${FILE}" ] + then + "${FILE}" + fi +done + +# Run +Top () +{ + HOME="$(tput cup 0 0)" + ED="$(tput ed)" + EL="$(tput el)" + + printf '%s%s' "${HOME}" "${ED}" + + while true + do + ROWS="$(tput lines)" + COLS="$(tput cols)" + CMD="${@}" + + ${SHELL:=sh} -c "${CMD}" | head -n ${ROWS} | while IFS= read LINE + do + printf '%-*.*s%s\n' ${COLS} ${COLS} "${LINE}" "${EL}" + done + + printf '%s%s' "${ED}" "${HOME}" + sleep ${DELAY} + done +} + +trap 'clear' EXIT HUP INT QUIT TERM + +Top "${PROGRAM} list && printf '%-59s %-19s\n' \" Host: ${HOST}\" \"\$(date +%Y-%m-%d\ %H:%M:%S)\"" + +# Post hooks +for FILE in "${HOOKS}/post-${COMMAND}".* "${HOOKS}/${NAME}.post-${COMMAND}" +do + if [ -x "${FILE}" ] + then + "${FILE}" + fi +done diff --git a/libexec/container/update b/libexec/container/update new file mode 100755 index 0000000..e2d9c80 --- /dev/null +++ b/libexec/container/update @@ -0,0 +1,270 @@ +#!/bin/sh + +# Copyright (C) 2014-2022 Daniel Baumann <daniel.baumann@open-infrastructure.net> +# +# SPDX-License-Identifier: GPL-3.0+ +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. + +set -e + +PROJECT="open-infrastructure" +SOFTWARE="compute-tools" +PROGRAM="container" +COMMAND="$(basename ${0})" + +HOOKS="/etc/${SOFTWARE}/hooks" + +Parameters () +{ + GETOPT_LONGOPTIONS="name:,full-upgrade,interactive,autoremove,purge,yes," + GETOPT_OPTIONS="n:,f,i,r,p,y," + + PARAMETERS="$(getopt --longoptions ${GETOPT_LONGOPTIONS} --name=${COMMAND} --options ${GETOPT_OPTIONS} --shell sh -- ${@})" + + if [ "${?}" != "0" ] + then + echo "'${COMMAND}': getopt exit" >&2 + exit 1 + fi + + eval set -- "${PARAMETERS}" + + while true + do + case "${1}" in + -n|--name) + NAME="${2}" + shift 2 + ;; + + -f|--full-upgrade) + FULL_UPGRADE="true" + shift 1 + ;; + + -i|--interactive) + INTERACTIVE="true" + shift 1 + ;; + + -r|--autoremove) + AUTOREMOVE="true" + shift 1 + ;; + + -p|--purge) + PURGE="--purge" + shift 1 + ;; + + -y|--yes) + YES="-y" + shift 1 + ;; + + --) + shift 1 + break + ;; + + *) + echo "'${COMMAND}': getopt error" >&2 + exit 1 + ;; + esac + done +} + +Usage () +{ + echo "Usage: ${PROGRAM} ${COMMAND} -n|--name NAME [-f|--full-upgrade] [-i|--interactive] [-r|--autoremove] [-p|--purge] [-y|--yes]" >&2 + echo + echo "See ${COMMAND}(1), ${PROGRAM}(1) and ${PROJECT}(7) for more information." + + exit 1 +} + +Parameters "${@}" + +if [ -z "${NAME}" ] +then + Usage +fi + +Notification () +{ + TYPE="${1}" + NUMBER="${2}" + PACKAGES="${3}" + + if [ -z "${PACKAGES}" ] + then + return + fi + + CONTAINER_USER="${SUDO_USER:-${USER}}" + + DATE="$(date +%Y-%m-%d\ %H:%M:%S)" + HOST="$(hostname -f 2> /dev/null || hostname)" + + # logfile + echo "${DATE} ${HOST} ${CONTAINER_USER} ${NAME} ${NUMBER} ${TYPE}: ${PACKAGES}" >> "/var/log/${SOFTWARE}/${PROGRAM}.log" + + # irc + if [ -e /usr/bin/irk ] + then + for FILE in "/etc/${SOFTWARE}/${PROGRAM}.conf" "/etc/${SOFTWARE}/${PROGRAM}.conf.d"/*.conf + do + if [ -e "${FILE}" ] + then + . "${FILE}" + fi + done + + if [ -n "${IRK_TARGETS}" ] + then + for TARGET in ${IRK_TARGETS} + do + irk "${TARGET}" "\x0300${CONTAINER_USER}\x03@\x0312${HOST}:\x03 \x0303${NAME}\x03 \x0307${NUMBER} ${TYPE}\x03: ${PACKAGES}" + done + fi + fi +} + +case "${NAME}" in + ALL) + NAMES="$(container list --started --format shell)" + ;; + + *) + NAMES="${NAME}" + ;; +esac + +# Pre hooks +for FILE in "${HOOKS}/pre-${COMMAND}".* "${HOOKS}/${NAME}.pre-${COMMAND}" +do + if [ -x "${FILE}" ] + then + "${FILE}" + fi +done + +if [ $(echo ${NAMES} | wc -w) -gt 1 ] +then + NAME_LOOP="true" +else + NAME_LOOP="false" +fi + +# Run +for NAME in ${NAMES} +do + case "${INTERACTIVE}" in + true) + case "${NAME_LOOP}" in + true) + echo + ;; + esac + + echo -n "'${NAME}': update container '${NAME}' [y|N|a]? " + read UPDATE + + UPDATE="$(echo ${UPDATE} | tr '[A-Z]' '[a-z]')" + + case "${UPDATE}" in + a|all) + INTERACTIVE="false" + ;; + + y|yes) + ;; + + *) + case "${NAME_LOOP}" in + true) + continue + ;; + + *) + exit 1 + ;; + esac + ;; + esac + ;; + esac + + echo "################################################################################" + echo "Updating ${NAME}" + echo "################################################################################" + + container run -n ${NAME} -- "apt update" + + UPDATE_NUMBER="$(container run -n ${NAME} -- "apt \-\-simulate full-upgrade" | awk '/^[0-9]* upgraded, / { print $1 }')" + + case "${UPDATE_NUMBER}" in + 0) + ;; + + *) + # usefull use of grep to de-colorize apt output + UPDATE_PACKAGES="$(for PACKAGE in $(container run -n ${NAME} -- "apt list \-\-upgradable 2>/dev/null | grep '\/'" | awk -F/ '{ print $1 }'); do echo -n "${PACKAGE} "; done | sed -e 's| $||'; echo)" + + case "${FULL_UPGRADE}" in + true) + container run -n ${NAME} -- "DEBCONF_FRONTEND='noninteractive' DEBCONF_PRIORITY='critical' DEBCONF_NONINTERACTIVE_SEEN='true' DEBCONF_NOWARNINGS='true' apt \-o Dpkg::Options::=\-\-force-confold -f ${YES} full-upgrade" + ;; + + *) + container run -n ${NAME} -- "DEBCONF_FRONTEND='noninteractive' DEBCONF_PRIORITY='critical' DEBCONF_NONINTERACTIVE_SEEN='true' DEBCONF_NOWARNINGS='true' apt \-o Dpkg::Options::=\-\-force-confold -f ${YES} upgrade" + ;; + esac + + Notification "update(s)" "${UPDATE_NUMBER}" "${UPDATE_PACKAGES}" + ;; + esac + + case "${AUTOREMOVE}" in + true) + REMOVE_NUMBER="$(container run -n ${NAME} -- "apt \-\-simulate autoremove" | awk '/^[0-9]* upgraded, / { print $6 }')" + + case "${REMOVE_NUMBER}" in + 0) + ;; + + *) + REMOVE_PACKAGES="$(for LINE in $(container run -n ${NAME} -- "apt \-\-simulate autoremove" | grep '^ '); do echo ${LINE}; done | sed -e 's|^ ||' -e 's|
$||'; echo)" + + container run -n ${NAME} -- "apt ${YES} autoremove ${PURGE}" + + Notification "removal(s)" "${REMOVE_NUMBER}" "$(echo ${REMOVE_PACKAGES})" + ;; + esac + ;; + esac + + echo "'${NAME}': container updated." +done + +# Post hooks +for FILE in "${HOOKS}/post-${COMMAND}".* "${HOOKS}/${NAME}.post-${COMMAND}" +do + if [ -x "${FILE}" ] + then + "${FILE}" + fi +done diff --git a/libexec/container/version b/libexec/container/version new file mode 100755 index 0000000..e580688 --- /dev/null +++ b/libexec/container/version @@ -0,0 +1,49 @@ +#!/bin/sh + +# Copyright (C) 2014-2022 Daniel Baumann <daniel.baumann@open-infrastructure.net> +# +# SPDX-License-Identifier: GPL-3.0+ +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. + +set -e + +PROJECT="open-infrastructure" +SOFTWARE="compute-tools" +PROGRAM="container" +COMMAND="$(basename ${0})" + +HOOKS="/etc/${SOFTWARE}/hooks" +SHARE="/usr/share/${SOFTWARE}" + +# Pre hooks +for FILE in "${HOOKS}/pre-${COMMAND}".* "${HOOKS}/${NAME}.pre-${COMMAND}" +do + if [ -x "${FILE}" ] + then + "${FILE}" + fi +done + +# Run +cat "${SHARE}/VERSION.txt" + +# Post hooks +for FILE in "${HOOKS}/post-${COMMAND}".* "${HOOKS}/${NAME}.post-${COMMAND}" +do + if [ -x "${FILE}" ] + then + "${FILE}" + fi +done |