#!/bin/sh # Copyright 2022 Mir Calculate. http://www.calculate-linux.org # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. TEXTDOMAIN=cl_lxc export PATH="/lib/rc/bin:$PATH" set -ueo pipefail check_simultaneous_run() { # Проверим на повторный запуск lock=/tmp/cl-lxc.lock if ! mkdir $lock &>/dev/null then echo $"Script launched!" >&2 exit 1 fi trap "rm -rf $lock" EXIT true } print_help(){ usage=$(printf $"SYNOPSIS: %s [PARAMETERS?] [name]" ${0##*/}) if [[ "$1" == "all" ]] then echo $"$usage ${0##*/} installation, configuration and update of an LXC container with Calculate Linux on board Parameters: -D, --distro select distribution flavour ('list' to view all that applies) -c, --create create new container --path path to container -p, --prepare get rootfs ready for update -u, --upgrade update container rootfs --diff diff two rootfs packages -R, --clear_cache clear cache -m, --mirror mirror address -h, --help show this help page " else echo $usage fi true } get_args(){ check_val(){ if [[ "$#" == "1" || "$2" =~ ^- ]] then echo $"Parameter '$1' contains no value!" >&2 exit 1 fi } distro_= create_=0 path_= prepare_=0 upgrade_=0 diff_=0 clear_=0 mirror_=mirror.calculate-linux.org while (( $# > 0 )) do case "$1" in -D|--distro) check_val $@ distro_=$2 shift ;; -c|--create) create_=1 ;; --path) check_val $@ path_=$2 shift ;; -p|--prepare) prepare_=1 ;; -u|--upgrade) upgrade_=1 ;; --diff) diff_=1 ;; -R|--clear_cache) clear_=1 ;; -m|--mirror) check_val $@ mirror_=$2 shift ;; -h|--help) print_help "all" exit ;; --) shift break ;; *) if ! [[ $1 =~ ^- ]] then break fi printf $"Invalid option '%s'.\n" $1 >&2 exit 1 ;; esac shift done if [ "$distro_" == 'list' ] then print_distro exit 0 elif (( $# == 1 )) then lxc_=$1 if [[ $lxc_ =~ [^A-Za-z0-9_\.] ]] then echo $"Container name contains wrong characters." >&2 exit 1 fi else print_help "usage" exit 1 fi true } print_distro(){ if (( $# == 0 )) then echo $"Distributive: [CCS] Calculate Container Scratch (by default) [CCG] Calculate Container Games" return fi case "$1" in CCS) echo "Calculate Container Scratch" ;; CCG) echo "Calculate Container Games" ;; esac } set_vars(){ lxc_release=22.0.1 net_domain=$(hostname -f) path_def=$(lxc-config lxc.lxcpath) if ! [[ $prepare_ == 1 || $upgrade_ == 1 ]] then name_lxc=$lxc_ name_upgrading= name_rootfs= status_lxc= else name_lxc="${lxc_}_new" name_upgrading=$lxc_ name_rootfs="rootfs-${lxc_}" if [[ $(lxc-info -s $lxc_ 2>&1 | grep RUNNING) ]] then status_lxc='RUNNING' else status_lxc='STOPPED' fi fi if [ -L $path_def/$lxc_ ] then path_work=$(sed 's/\/[^/]*$//' <<< $(realpath $path_def/$lxc_)) if [ -n "$path_" ] && [ $path_ != "$path_work" ] then printf $"Wrong --path parameter, container created in '%s'.\n" $path_work >&2 exit 1 fi elif [ -n "$path_" ] && [ "$path_" != "$path_def" ] then path_work=$path_ else path_work=$path_def fi path_lxc=$path_work/$name_lxc path_cache=$path_work/.cache path_lxc_prepare=$path_cache/rootfs-$lxc_ if [ $path_def = "$path_work" ] then path_change= else path_change=$path_work fi type_fs=$(df -Th $path_work | awk 'NR==2 {print $2}') if [[ $prepare_ == 1 || $upgrade_ == 1 ]] && [ -f $path_work/$lxc_/config ] then # Считаем номер текущего корня обновляемой системы и определим новый num_cur=$(grep lxc.rootfs.path $path_work/$lxc_/config | sed 's/.*rootfs//') num_next=$(ls -d $path_work/$lxc_/rootfs* | sed 's/.*rootfs//' | sort -n | tail -n 1) let "num_next+=1" else num_cur= num_next= fi case "$distro_" in CCS|ccs) lxc_distro=CCS ;; CCG|ccg) lxc_distro=CCG ;; '') if [ $prepare_ == 1 ] || [ $upgrade_ == 1 ] then lxc_distro=$(file $path_work/$lxc_/rootfs$num_cur/etc/portage/make.profile | \ awk -F '/profiles/' '{ print $2 }' | awk -F '/' '{ print $1 }') else lxc_distro=CCS fi ;; *) echo $"Wrong --distro parameter, use 'list' to view accepted values." >&2 exit 1 ;; esac true } debug_vars(){ local log=/var/log/calculate/cl-lxc.log cat > /var/log/calculate/cl-lxc.log <&2 exit 1 fi if [ $create_ == 1 ] && [[ -e $path_lxc || -e $path_def/$name_lxc ]] then printf $"Wrong parameter %s, container '%s' exists already!\n" --create $name_lxc >&2 exit 1 fi if [ $create_ == 0 ] && [ -n "$path_" ] then printf $"Parameter %s should be used to create a container.\n" --path >&2 exit 1 fi if [[ $prepare_ == 1 || $upgrade_ == 1 ]] && [ ! -e $path_work/$name_upgrading ] then local param_name='--upgrade' if [ $prepare_ == 1 ] then param_name='--prepare' fi printf $"Wrong parameter %s, container '%s' does not exist!\n" $param_name $name_upgrading >&2 exit 1 fi if [[ $upgrade_ == 1 && $prepare_ == 0 && ! -d $path_lxc_prepare ]] || [[ $upgrade_ == 1 && $prepare_ == 0 && $clear_ == 1 ]] then printf $"Before update, prepare an image using option %s. This operation may be combined with others.\n" \ --prepare >&2 exit 1 fi if [ $create_ == 0 ] && [ $prepare_ == 0 ] && [ $upgrade_ == 0 ] then if [ ! -e $path_lxc ] then printf $"No operation specified. Call %s to create a container.\n" \ "'$program_name -c $name_lxc'" >&2 exit 1 elif [ ! -d $path_lxc_prepare ] then printf $"No operation specified. Call %s to prepare an update image for the container.\n" \ "'$program_name -p $name_lxc'" >&2 exit 1 elif [ -d $path_lxc_prepare ] then printf $"No operation specified. Call %s to update the container.\n" \ "'$program_name -u $name_lxc'" >&2 exit 1 else print_help "usage" >&2 exit 1 fi fi if [ $path_def != '/var/calculate/lxc' ] then printf $"lxc.lxcpath must contain the path to '/var/calculate/lxc'.\n" >&2 exit 1 fi true } create_base() { ebegin $"Downloading" $(print_distro $lxc_distro) # Создадим подтом, чтобы контейнер не участвовал в бэкапе btrbk if [ ! -e $path_cache ] then if [ $type_fs = 'btrfs' ] then btrfs subvolume create $path_cache &>/dev/null else mkdir $path_cache fi fi # Удалим кэш LXC if [ $clear_ == 1 ] then rm -rf /var/cache/lxc/download/$lxc_distro fi lxc-create -n $lxc_distro -t download -- --server $mirror_ \ --no-validate --arch x86_64 --dist $lxc_distro --release $lxc_release &>/dev/null || { eend $? || true eerror $"Failed to run lxc-create" >&2 rm -rf $path_cache exit $? } eend mv $path_def/$lxc_distro $path_cache/$lxc_distro true } create_lxc() { echo $"Creating container $name_lxc" if [ $type_fs = 'btrfs' ] then btrfs subvolume create $path_lxc >/dev/null || { eend $? || true eerror $"Failed to create subvolume $path_lxc" >&2 exit $? } else mkdir $path_lxc fi chmod 700 $path_lxc local reflink='' && [ $type_fs = 'btrfs' ] && reflink='--reflink' if [ -d "$path_lxc_prepare" ] then ebegin $"Restoring rootfs image from cache" cp -a $reflink $path_lxc_prepare $path_lxc/rootfs eend else ebegin $"Copying base rootfs" cp -a $reflink $path_cache/$lxc_distro/rootfs $path_lxc/rootfs eend fi if [ -n "$path_change" ] then ln -s $path_lxc $path_def/$name_lxc fi ebegin $"Preparing base mount paths" rm -rf $path_lxc/rootfs/usr/portage; mkdir $path_lxc/rootfs/usr/portage rm -rf $path_lxc/rootfs/var/db/repos/calculate; mkdir $path_lxc/rootfs/var/db/repos/calculate rm -rf $path_lxc/rootfs/var/cache/edb/binhost; mkdir $path_lxc/rootfs/var/cache/edb/binhost rm -rf $path_lxc/rootfs/var/calculate/packages; mkdir $path_lxc/rootfs/var/calculate/packages rm -rf $path_lxc/rootfs/var/calculate/distfiles;mkdir $path_lxc/rootfs/var/calculate/distfiles eend if [ $prepare_ == 0 ] then ebegin $"Moving /var/calculate to a separate mount point" # Очистка и создание пути для монтирования ресурсов из хостовой машины mv $path_lxc/rootfs/var/calculate $path_lxc; mkdir $path_lxc/rootfs/var/calculate calculate_mount="lxc.mount.entry = ${path_lxc}/calculate var/calculate none rw,bind 0 0 " calculate_dir="${path_lxc}/calculate" eend else calculate_mount="" calculate_dir="${path_lxc}/rootfs/var/calculate" fi local container_mount="" if ! [ -d /var/db/repos/container ] then ewarn $"Skipping mounting Container overlay" container_mount="#lxc.mount.entry = /var/db/repos/container var/db/repos/container none ro,bind 0 0 " else rm -rf $path_lxc/rootfs/var/db/repos/container; mkdir $path_lxc/rootfs/var/db/repos/container container_mount="lxc.mount.entry = /var/db/repos/container var/db/repos/container none ro,bind 0 0 " fi ebegin $"Running container setup" local net_conf= if [ $prepare_ == 1 ] || [ ! -L /sys/class/net/br0 ] then net_conf=none network_conf="lxc.net.0.type = none" cp /etc/resolv.conf $path_lxc/rootfs/etc else net_conf=veth local random_mac=$(echo -n '02:'; hexdump -n5 -e '/1 ":%02X"' /dev/random | sed s/^://g) network_conf="lxc.net.0.type = veth lxc.net.0.flags = up lxc.net.0.name = eth0 lxc.net.0.link = br0 lxc.net.0.hwaddr = ${random_mac} lxc.net.0.veth.pair = lxc-${name_lxc}" fi # перенесем базовый config удалив настройку сети cp $path_cache/$lxc_distro/config ${path_lxc}/config sed -i '/lxc.net.0.type = empty/d' ${path_lxc}/config sed -i "s/${lxc_distro}/${name_lxc}/g" ${path_lxc}/config cat << EOL >> ${path_lxc}/config ${network_conf} ${calculate_mount}lxc.mount.entry = /usr/portage usr/portage none ro,bind 0 0 lxc.mount.entry = /var/db/repos/calculate var/db/repos/calculate none ro,bind 0 0 ${container_mount}lxc.mount.entry = /var/cache/edb/binhost var/cache/edb/binhost none ro,bind 0 0 lxc.mount.entry = /var/calculate/packages var/calculate/packages none rw,bind 0 0 lxc.mount.entry = /var/calculate/distfiles var/calculate/distfiles none rw,bind 0 0 EOL eend ebegin $"Configuring utility variables" cat << EOL > ${calculate_dir}/calculate.env [install] os_install_net_hostname = ${name_lxc} os_install_net_domain = ${net_domain} os_install_locale_lang = ${LANG/.utf8/} os_install_clock_timezone = $(cat /etc/timezone) [update] cl_update_rep_name = cl_update_rep_url = cl_update_rep_sync = cl_update_eixupdate_force = force cl_update_force_fix_set = on EOL eend ebegin $"Creating templates" mkdir -p ${calculate_dir}/templates/default cat << EOL > ${calculate_dir}/templates/default/.calculate_directory # Calculate env=install ac_install_live==on append=skip EOL if [ $net_conf == veth ] then cat << EOL > ${calculate_dir}/templates/default/runlevel.eth0 # Calculate mergepkg(sys-apps/openrc)!= path=/etc/runlevels/default name=net.eth0 protected link=/etc/init.d/net.lo symbolic EOL fi cat << EOL > ${calculate_dir}/templates/default/portage.binhost # Calculate mergepkg(sys-apps/portage)!= path=/etc/portage/make.conf name=binhost protected comment=# $(emerge --info | grep PORTAGE_BINHOST) EOL eend [ -z "$name_upgrading" ] && printf $"Your container is ready. To start it, please run 'lxc-start %s'.\n" $name_lxc true } update_prepare() { printf $"Package update for container %s\n" $name_lxc ebegin $"Moving installed packages, flags and masks from main container" cp $path_work/$name_upgrading/rootfs$num_cur/etc/portage/sets/custom $path_lxc/rootfs/etc/portage/sets/custom &>/dev/null || true cp $path_work/$name_upgrading/rootfs$num_cur/etc/portage/package.accept_keywords/custom \ $path_lxc/rootfs/etc/portage/package.accept_keywords/custom &>/dev/null || true cp $path_work/$name_upgrading/rootfs$num_cur/etc/portage/package.use/custom \ $path_lxc/rootfs/etc/portage/package.use/custom &>/dev/null || true cp $path_work/$name_upgrading/rootfs$num_cur/etc/portage/package.mask/custom \ $path_lxc/rootfs/etc/portage/package.mask/custom &>/dev/null || true cp $path_work/$name_upgrading/rootfs$num_cur/etc/portage/package.unmask/custom \ $path_lxc/rootfs/etc/portage/package.unmask/custom &>/dev/null || true eend ebegin $"Creating mount points" for mount_point in $(grep lxc.mount.entry $path_work/$name_upgrading/config | awk {'print $4'}) do mkdir -p "$path_lxc/rootfs/$mount_point" done eend ebegin $"Starting container" lxc-start $name_lxc # ожидание первоначальной настройки контейнера while ! lxc-attach $name_lxc -- ps ax | grep 'init \[3\]' > /dev/null do sleep 0.1 done eend cl-update -s einfo $"Launching package update" lxc-attach $name_lxc -- cl-update -f ebegin $"Stopping container" lxc-stop $name_lxc eend ebegin $"Saving image for update" if [ -d $path_lxc_prepare ] then rm -r $path_lxc_prepare fi mv $path_lxc/rootfs $path_lxc_prepare eend ebegin $"Removing temporary container directory $name_lxc" if [ $type_fs = 'btrfs' ] then btrfs subvolume delete $path_lxc >&/dev/null if [ -n "$path_change" ] then rm $path_def/$name_lxc # удалим символическую ссылку fi else rm -rf $path_lxc fi eend [ $diff_ == 1 ] && diff_pkg einfo $"Image for update ready. Now you can run '$program_name -u $name_upgrading' to update." return } upgrade_lxc() { echo $"Preparing to start new root" ebegin $"Moving image to $name_upgrading/rootfs$num_next" local reflink= if [ $type_fs = 'btrfs' ] then reflink='--reflink' fi cp -a $reflink $path_lxc_prepare $path_work/$name_upgrading/rootfs$num_next eend ebegin $"Copying network settings from main container" cp $path_work/$name_upgrading/rootfs$num_cur/etc/conf.d/net \ $path_work/$name_upgrading/rootfs$num_next/etc/conf.d/net eend ebegin $"Preparing first boot setup script" cat << EOL > $path_work/$name_upgrading/rootfs$num_next/etc/local.d/firststart.start cl-core --method setup_system --no-progress --usenew-conf --network on openrc #rm /etc/local.d/firststart.start EOL chmod 755 $path_work/$name_upgrading/rootfs$num_next/etc/local.d/firststart.start eend ebegin $"New root setup" sed -i "s#$path_def/$name_upgrading/rootfs[0-9]*#$path_def/$name_upgrading/rootfs$num_next#" $path_work/$name_upgrading/config eend if [ ! -z $(lxc-ls --running --filter "^$name_upgrading$") ] then einfo $"Stopping container $name_upgrading" lxc-stop $name_upgrading eend fi if [ "$status_lxc" = 'RUNNING' ] then einfo $"Launching container $name_upgrading" lxc-start $name_upgrading eend fi [ $diff_ == 1 ] && diff_pkg # Отобразим разницу в пакетах while ! (test -a $path_work/$name_upgrading/rootfs$num_next/etc/resolv.conf) do sleep 0.1 done einfo $(lxc-info -i $name_upgrading) printf $"Container updated." if [ "$status_lxc" = 'STOPPED' ] then printf ' ' printf $"To start it, please run 'lxc-start %s'.\n" $name_upgrading else echo fi true } diff_pkg() { # Считаем версии сборок cur_ver=$(grep os_linux_build $path_work/$name_upgrading/rootfs$num_cur/var/lib/calculate/calculate.env | sed 's/.*os_linux_build\s*=\s*//') next_ver=$(grep os_linux_build $path_lxc_prepare/var/lib/calculate/calculate.env | sed 's/.*os_linux_build\s*=\s*//') printf $"Diffs between CSS %s and %s versions:\n" $cur_ver $next_ver find $path_work/$name_upgrading/rootfs$num_cur/var/db/pkg/ -type d | sed 's/.*db\/pkg\///' | grep \/ | sort > $lock/cur find $path_lxc_prepare/var/db/pkg/ -type d | sed 's/.*db\/pkg\///' | grep \/ | sort > $lock/new cp $lock/new $lock/work while IFS= read -r pkg do if grep -q $pkg$ $lock/cur then sed -i "/$pkg/d" $lock/new sed -i "/$pkg/d" $lock/cur fi done <<< $(awk -F / '{print $2}' $lock/work) if [ ! -s $lock/new ] && [ ! -s $lock/cur ] then einfo $"No difference detected" else diff $lock/new $lock/cur || true echo fi true } # Проверка одновременного запуска check_simultaneous_run # Обработка параметров вызова скрипта save=$IFS IFS=$' =\n' get_args $@ IFS=$save # Установка значений переменных set_vars # Логирование значений переменных debug_vars # Проверка параметров запуска check_vars # Чистка кэша базового контейнера # пустой файл partial когда lxc-create был прерван if [[ $clear_ == 1 && -d "$path_cache/$lxc_distro" ]] then ebegin $"Clearing Calculate Container cache" rm -rf "$path_cache/$lxc_distro" eend fi # Создание базового контейнера if [[ $create_ == 1 || $prepare_ == 1 || $clear_ == 1 ]] && [ ! -d "$path_cache/$lxc_distro" ] then create_base fi # Создание контейнера if [[ $create_ == 1 || $prepare_ == 1 ]] then if [ -d "$path_lxc_prepare" ] then if [ $clear_ == 1 ] then ebegin $"Clearing container cache" rm -r "$path_lxc_prepare" eend fi fi create_lxc fi # Обновление пакетов контейнера if [ $prepare_ == 1 ] then update_prepare fi # Обновление контейнера if [ $upgrade_ == 1 ] then upgrade_lxc fi true