#!/usr/bin/env bash ### # # Options Section # ### set -e set -u set -o pipefail ### # # Function Section # ### function ct_check () { message 'Check container consistency' /usr/bin/test -d "${CT_REALPATH}" || \ error '\r\rCheck container consistency \xE2\x9D\x8C\n' \ ' \xE2\x86\x92 Error: The selected folder can not be found.\n' message '\r\rCheck container consistency \xE2\x9C\x94\n' } function error () { if [ -n "${1:-}" ]; then /usr/bin/echo -n -e "${1}" fi if [ -n "${2:-}" ]; then /usr/bin/echo -n -e "${2}" fi exit 1 } function help () { local HELP case "${1}" in command) read -r -d '' HELP << EOL Usage: pve-lxc (OPTION) (CONTAINER) Options: [ -a ] [ CONTAINER ] | Archive Container [ -c ] [ CONTAINER ] | Chroot Container [ -h ] | Help [ -p ] [ CONTAINER ] | Prepare Container [ -r ] [ CONTAINER ] | Container Repository EOL ;; archive) read -r -d '' HELP << EOL Usage: pve-lxc -a (CONTAINER) Description: Compresses the container with the data compression format 'xz' and generates a hash sum of it. EOL ;; chroot) read -r -d '' HELP << EOL Usage: pve-lxc -c (CONTAINER) Description: Change the root environment into the container. EOL ;; prepare) read -r -d '' HELP << EOL Usage: pve-lxc -p (CONTAINER) Description: Prepare and/or clean up the container. EOL ;; repository) read -r -d '' HELP << EOL Usage: pve-lxc -r (CONTAINER) Description: Download one of the following images from the repository. - Debian GNU/Linux [ARM64] - arm64_debian_bookworm - Ubuntu GNU/Linux [ARM64] - arm64_ubuntu_noble-numbat EOL ;; esac /usr/bin/echo -e "${HELP}" } function message () { if [ -n "${1:-}" ]; then /usr/bin/echo -n -e "${1}" fi } ### # # Runtime Environment # ### /usr/bin/echo -e 'Proxmox Virtual Environment Container Tool\n' if [[ "${EUID}" -ne '0' ]]; then /usr/bin/echo -e 'Error: Permission Denied' exit 1 fi while getopts ':a:c:hp:r:' OPT; do case "${OPT}" in a) CT_BASENAME=$(/usr/bin/basename "${OPTARG}") CT_REALPATH=$(/usr/bin/realpath "${OPTARG}") CT_DIRNAME=$(/usr/bin/dirname "${CT_REALPATH}") ct_check message 'Compress container' /usr/bin/tar --create --use-compress-program="/usr/bin/xz -9 --extreme" --file "${CT_DIRNAME}/${CT_BASENAME}.tar.xz" --directory="${CT_REALPATH}" '.' || \ error '\r\rGenerate container \xE2\x9D\x8C\n' \ ' \xE2\x86\x92 Error: The container path can not be compressed.\n' message '\r\rGenerate container \xE2\x9C\x94\n' message 'Generate hash' /usr/bin/sha512sum "${CT_DIRNAME}/${CT_BASENAME}.tar.xz" | /usr/bin/sed --expression='s, .*/, ,' >> "${CT_DIRNAME}/${CT_BASENAME}.tar.xz.sha512sum" || \ error '\r\rGenerate hash \xE2\x9D\x8C\n' \ ' \xE2\x86\x92 Error: The container hash can not be generated.\n' message '\r\rGenerate hash \xE2\x9C\x94\n' exit 0 ;; c) CT_BASENAME=$(/usr/bin/basename "${OPTARG}") CT_REALPATH=$(/usr/bin/realpath "${OPTARG}") ct_check message "Bind filesystem '/dev'" if ! /usr/bin/grep --quiet "${CT_REALPATH}/dev" '/proc/mounts'; then /usr/bin/mount --bind '/dev' --target "${CT_REALPATH}/dev" &> '/dev/null' || \ error "\r\rBind filesystem '/dev' \xE2\x9D\x8C\n" \ ' \xE2\x86\x92 Error: The filesystem can not be binded.\n' fi message "\r\rBind filesystem '/dev' \xE2\x9C\x94\n" message "Bind filesystem '/dev/pts'" if ! /usr/bin/grep --quiet "${CT_REALPATH}/dev/pts" '/proc/mounts'; then /usr/bin/mount --bind '/dev/pts' --target "${CT_REALPATH}/dev/pts" &> '/dev/null' || \ error "\r\rBind filesystem '/dev/pts' \xE2\x9D\x8C\n" \ ' \xE2\x86\x92 Error: The filesystem can not be binded.\n' fi message "\r\rBind filesystem '/dev/pts' \xE2\x9C\x94\n" message "Bind filesystem '/dev/mqueue'" if ! /usr/bin/grep --quiet "${CT_REALPATH}/dev/mqueue" '/proc/mounts'; then /usr/bin/mount --bind '/dev/mqueue' --target "${CT_REALPATH}/dev/mqueue" &> '/dev/null' || \ error "\r\rBind filesystem '/dev/mqueue' \xE2\x9D\x8C\n" \ ' \xE2\x86\x92 Error: The filesystem can not be bounded.\n' fi message "\r\rBind filesystem '/dev/mqueue' \xE2\x9C\x94\n" message "Slave filesystem '/dev'" if ! /usr/bin/grep --quiet "${CT_REALPATH}/dev" '/proc/mounts'; then /usr/bin/mount --make-rslave --target "${CT_REALPATH}/dev" &> '/dev/null' || \ error "\r\rSlave filesystem '/dev' \xE2\x9D\x8C\n" \ ' \xE2\x86\x92 Error: The filesystem can not be slaved.\n' fi message "\r\rSlave filesystem '/dev' \xE2\x9C\x94\n" message "Bind filesystem '/proc'" if ! /usr/bin/grep --quiet "${CT_REALPATH}/proc" '/proc/mounts'; then /usr/bin/mount --types 'proc' --source '/proc' --target "${CT_REALPATH}/proc" &> '/dev/null' || \ error "\r\rBind filesystem '/proc' \xE2\x9D\x8C\n" \ ' \xE2\x86\x92 Error: The filesystem can not be bounded.\n' fi message "\r\rBind filesystem '/proc' \xE2\x9C\x94\n" message "Bind filesystem '/sys'" if ! /usr/bin/grep --quiet "${CT_REALPATH}/sys" '/proc/mounts'; then /usr/bin/mount --types 'sysfs' --source '/sys' --target "${CT_REALPATH}/sys" &> '/dev/null' || \ error "\r\rBind filesystem '/sys' \xE2\x9D\x8C\n" \ ' \xE2\x86\x92 Error: The filesystem can not be bounded.\n' fi message "\r\rBind filesystem '/sys' \xE2\x9C\x94\n" message 'Prepare System-Resolver' RESOLVCONF='0' if [[ -L "${CT_REALPATH}/etc/resolv.conf" ]]; then RESOLVCONF='1' /usr/bin/unlink "${CT_REALPATH}/etc/resolv.conf" || \ error '\r\rPrepare System-Resolver \xE2\x9D\x8C\n' \ ' \xE2\x86\x92 Error: The resolv.conf can not be unlinked.\n' fi /usr/bin/cp --force '/etc/resolv.conf' "${CT_REALPATH}/etc/resolv.conf" || \ error '\r\rPrepare System-Resolver \xE2\x9D\x8C\n' \ ' \xE2\x86\x92 Error: The resolv.conf can not be copied.\n' message '\r\rPrepare System-Resolver \xE2\x9C\x94\n' message '\n ... exit chroot environment ... \n\n' /usr/bin/sleep '3s' /usr/sbin/chroot "${CT_REALPATH}" || true message '\n ... exit chroot environment ... \n\n' /usr/bin/sleep '3s' message 'Reset System-Resolver' /usr/bin/rm --force "${CT_REALPATH}/etc/resolv.conf" || \ error '\r\rReset System-Resolver \xE2\x9D\x8C\n' \ ' \xE2\x86\x92 Error: The resolv.conf can not be removed.\n' if [[ "${RESOLVCONF}" == '1' ]]; then /usr/bin/ln --force --symbolic '../run/systemd/resolve/stub-resolv.conf' "${CT_REALPATH}/etc/resolv.conf" || \ error '\r\rReset System-Resolver \xE2\x9D\x8C\n' \ ' \xE2\x86\x92 Error: The resolv.conf can not be linked.\n' fi unset RESOLVCONF message '\r\rReset System-Resolver \xE2\x9C\x94\n' message "Unbind filesystem '/dev'" if /usr/bin/grep --quiet "${CT_REALPATH}/dev" '/proc/mounts'; then /usr/bin/umount --recursive "${CT_REALPATH}/dev" || \ error "\r\rUnbind filesystem '/dev' \xE2\x9D\x8C\n" \ ' \xE2\x86\x92 Error: The filesystem can not be unbound.\n' fi message "\r\rUnbind filesystem '/dev' \xE2\x9C\x94\n" message "Unbind filesystem '/proc'" if /usr/bin/grep --quiet "${CT_REALPATH}/proc" '/proc/mounts'; then /usr/bin/umount --recursive "${CT_REALPATH}/proc" || \ error "\r\rUnbind filesystem '/proc' \xE2\x9D\x8C\n" \ ' \xE2\x86\x92 Error: The filesystem can not be unbound.\n' fi message "\r\rUnbind filesystem '/proc' \xE2\x9C\x94\n" message "Unbind filesystem '/sys'" if /usr/bin/grep --quiet "${CT_REALPATH}/sys" '/proc/mounts'; then /usr/bin/umount --recursive "${CT_REALPATH}/sys" || \ error "\r\rUnbind filesystem '/sys' \xE2\x9D\x8C\n" \ ' \xE2\x86\x92 Error: The filesystem can not be unbound.\n' fi message "\r\rUnbind filesystem '/sys' \xE2\x9C\x94\n" exit 0 ;; h) set +e help 'command' exit 0 ;; p) CT_BASENAME=$(/usr/bin/basename "${OPTARG}") CT_REALPATH=$(/usr/bin/realpath "${OPTARG}") CT_DIRNAME=$(/usr/bin/dirname "${CT_REALPATH}") ct_check message 'Open filelist' if [ -f "/usr/lib/pve-lxc/${CT_BASENAME}.txt" ]; then /usr/bin/cp --update='none' "/usr/lib/pve-lxc/${CT_BASENAME}.txt" "${CT_DIRNAME}/${CT_BASENAME}.txt" || \ error '\r\rOpen filelist \xE2\x9D\x8C\n' \ ' \xE2\x86\x92 Error: The filelist can not be copied.\n' elif [[ ! -f "/usr/lib/pve-lxc/${CT_BASENAME}.txt" ]]; then /usr/bin/cp --update='none' "/usr/lib/pve-lxc/default.txt" "${CT_DIRNAME}/${CT_BASENAME}.txt" || \ error '\r\rOpen filelist \xE2\x9D\x8C\n' \ ' \xE2\x86\x92 Error: The filelist can not be copied.\n' fi /usr/bin/nano "${CT_DIRNAME}/${CT_BASENAME}.txt" || \ error '\r\rOpen filelist \xE2\x9D\x8C\n' \ ' \xE2\x86\x92 Error: The filelist can not be opened.\n' message '\r\rOpen filelist \xE2\x9C\x94\n' message 'Remove files' mapfile -t CLEANUP_LIST < <(/usr/bin/cat "${CT_DIRNAME}/${CT_BASENAME}.txt" | /usr/bin/sed '/#.*/d' | while read -r CLEANUP_PATH; do /usr/bin/echo "${CT_REALPATH}${CLEANUP_PATH}"; done) for CLEANUP_LIST in "${CLEANUP_LIST[@]}"; do /usr/bin/sh -c "/usr/bin/ls --directory ${CLEANUP_LIST} | /usr/bin/xargs /usr/bin/rm --force --recursive" &> '/dev/null' || \ error '\r\rRemove files \xE2\x9D\x8C\n' \ " \xE2\x86\x92 Error: The file or directory >${CLEANUP_LIST}< can not be removed.\n" done unset CLEANUP_LIST message '\r\rRemove files \xE2\x9C\x94\n' message 'Create configuration' /usr/bin/test -f "${CT_REALPATH}/etc/hostname" && \ /usr/bin/cp --force '/usr/lib/pve-lxc/hostname' "${CT_REALPATH}/etc/hostname" /usr/bin/test -f "${CT_REALPATH}/etc/hosts" && \ /usr/bin/cp --force '/usr/lib/pve-lxc/hosts' "${CT_REALPATH}/etc/hosts" /usr/bin/test -f "${CT_REALPATH}/etc/network/interfaces" && \ /usr/bin/cp --force '/usr/lib/pve-lxc/interfaces' "${CT_REALPATH}/etc/network/interfaces" /usr/bin/test -f "${CT_REALPATH}/etc/resolv.conf" && \ /usr/bin/cp --force '/usr/lib/pve-lxc/resolv.conf' "${CT_REALPATH}/etc/resolv.conf" message '\r\rCreate configuration \xE2\x9C\x94\n' message 'Clear logfiles' mapfile -t CLEANUP_LIST < <(/usr/bin/find "${CT_REALPATH}/var/log" -type 'f') for CLEANUP_LIST in "${CLEANUP_LIST[@]}"; do /usr/bin/dd if=/dev/null of="${CLEANUP_LIST}" &> '/dev/null' || \ error '\r\rClear logfiles \xE2\x9D\x8C\n' \ " \xE2\x86\x92 Error: The file >${CLEANUP_LIST}< can not be cleared.\n" done unset CLEANUP_LIST mapfile -t CLEANUP_LIST < <(/usr/bin/find "${CT_REALPATH}/var/logrotate" -type 'f') for CLEANUP_LIST in "${CLEANUP_LIST[@]}"; do /usr/bin/sh -c "/usr/bin/ls --directory ${CLEANUP_LIST} | /usr/bin/xargs /usr/bin/rm --force --recursive" &> '/dev/null' || \ error '\r\rClear logfiles \xE2\x9D\x8C\n' \ " \xE2\x86\x92 Error: The file >${CLEANUP_LIST}< can not be removed.\n" done message '\r\rClear logfiles \xE2\x9C\x94\n' exit 0 ;; r) message 'Download container' if [[ -d "./${OPTARG}" ]]; then error '\r\rDownload container \xE2\x9D\x8C\n' \ ' \xE2\x86\x92 Error: The container already exists.\n' fi /usr/bin/wget --quiet --output-document="./${OPTARG}.tar.xz" --no-hsts "https://repository.privlab.it/pve-lxc/${OPTARG}.tar.xz" &> '/dev/null' || \ error '\r\rDownload container \xE2\x9D\x8C\n' \ ' \xE2\x86\x92 Error: The container can not be downloaded.\n' /usr/bin/wget --quiet --output-document="./${OPTARG}.tar.xz.sha512sum" --no-hsts "https://repository.privlab.it/pve-lxc/${OPTARG}.tar.xz.sha512sum" &> '/dev/null' || \ error '\r\rDownload container \xE2\x9D\x8C\n' \ ' \xE2\x86\x92 Error: The container SHA512SUM can not be downloaded.\n' message '\r\rDownload container \xE2\x9C\x94\n' message 'Compare hash' /usr/bin/sha512sum --check --strict "./${OPTARG}.tar.xz.sha512sum" &> '/dev/null' || \ error '\r\rCompare hash \xE2\x9D\x8C\n' \ ' \xE2\x86\x92 Error: The hash can not be verified.\n' /usr/bin/rm --force "./${OPTARG}.tar.xz.sha512sum" || \ error '\r\rCompare hash \xE2\x9D\x8C\n' \ ' \xE2\x86\x92 Error: The hash can not be removed.\n' message '\r\rCompare hash \xE2\x9C\x94\n' message 'Decompress container' /usr/bin/install --directory "./${OPTARG}" || \ error '\r\rDecompress container \xE2\x9D\x8C\n' \ ' \xE2\x86\x92 Error: The container path can not be installed.\n' /usr/bin/tar --extract --file="./${OPTARG}.tar.xz" --directory="./${OPTARG}" || \ error '\r\rDecompress container \xE2\x9D\x8C\n' \ ' \xE2\x86\x92 Error: The container can not be decompressed.\n' /usr/bin/rm --force "./${OPTARG}.tar.xz" || \ error '\r\rDecompress container \xE2\x9D\x8C\n' \ ' \xE2\x86\x92 Error: The compressed container can not be removed.\n' message '\r\rDecompress container \xE2\x9C\x94\n' exit 0 ;; -*|*) set +e case "${1}" in -a) help 'archive' exit 0 ;; -c) help 'chroot' exit 0 ;; -p) help 'prepare' exit 0 ;; -r) help 'repository' exit 0 ;; esac help 'command' exit 0 ;; esac done set +e help 'command' shift $((OPTIND-1))