You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
calculate-toolkit/cl-lxc

727 lines
18 KiB

#!/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 <<EOF
lxc_release = $lxc_release
net_domain = $net_domain
path_def = $path_def
name_lxc = $name_lxc
name_upgrading = $name_upgrading
name_rootfs = $name_rootfs
status_lxc = $status_lxc
path_work = $path_work
path_lxc = $path_lxc
path_cache = $path_cache
path_lxc_prepare = $path_lxc_prepare
path_change = $path_change
type_fs = $type_fs
num_cur = $num_cur
num_next = $num_next
lxc_distro = $lxc_distro
EOF
}
check_vars(){
program_name=${0##*/}
if [ "$path_" != '' ] && ! [ -d "$path_" ]
then
printf $"Failed to find directory '%s'.\n" $path_ >&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