#!/usr/bin/env bash
#
# Initial script for UBLinux
# This script are launching before starting init from initrd script
# Current dir allways must be set to root (/)
# All system path must be relative, except initrd dirs

ENABLED=yes
[[ ${ENABLED} == "yes" ]] || exit 0
DEBUGMODE=no

PATH=.:/:/usr/bin:/usr/local/bin:/usr/local/sbin

[[ -d /usr/lib/ublinux ]] && { unset ROOTFS; unset CMD_CHROOT; } || { ROOTFS="/sysroot"; CMD_CHROOT="chroot ${ROOTFS}"; }
SOURCE=${ROOTFS}/usr/lib/ublinux/functions; [[ -f ${SOURCE} ]] && . ${SOURCE} 2>/dev/null || exit 0
SOURCE=${ROOTFS}/usr/lib/ublinux/default; [[ -f ${SOURCE} ]] && . ${SOURCE} 2>/dev/null || exit 0
debug_mode "$0" "$@"

SYSCONF="${ROOTFS}${SYSCONF}"
SOURCE=${SYSCONF}/config; [[ -f ${SOURCE} ]] && . ${SOURCE} 2>/dev/null
SOURCE=${SYSCONF}/logging; [[ -f ${SOURCE} ]] && . ${SOURCE} 2>/dev/null

## Настройка мониторинга и сбора системных событий и записи их в журналы для аудита
## AUDITD=disable|no|none|off   # Отключить все созданные правила из конфигурации
## AUDITD[<id_name>]=<rule>
##   <id_name>          # Уникальное имя правила
##   <rule>             # Правило 
#AUDITD[event_chmod]="-a always,exit -F arch=x86_64 -S chmod,fchmod,fchmodat -F key=event_chmod"
#AUDITD[passwd_changes]="-w /etc/passwd -p wa -k passwd_changes"
exec_auditd(){
    [[ $1 == @("set="|"set+="|"set++="|"set-="|"set--="|"remove") ]] && COMMAND=$1 && shift
    [[ -n ${COMMAND} ]] || COMMAND="set="
    FILE_PATTERN_AUDITD_CONF="${ROOTFS}/etc/audit/rules.d/00-ubconfig.rules"
    local PARAM="$@"
    if [[ -n ${PARAM} ]]; then
	AUDITD_NAME=${PARAM%%=*}
	AUDITD_VAR=${PARAM#*=}
    fi
    [[ -d ${FILE_PATTERN_AUDITD_CONF%/*} ]] || mkdir -p ${FILE_PATTERN_AUDITD_CONF%/*}
    [[ -f ${FILE_PATTERN_AUDITD_CONF} ]] || true > "${FILE_PATTERN_AUDITD_CONF}"
    if [[ -z ${PARAM} ]]; then
	true > "${FILE_PATTERN_AUDITD_CONF}"
        while IFS='=' read -u3 AUDITD_NAME AUDITD_VAR; do
    	    [[ ${AUDITD_NAME} =~ ^.*'['(.*)']' ]] && AUDITD_NAME=${BASH_REMATCH[1]}
    	    [[ ${AUDITD_VAR} =~ ^\"(.*)\"$ ]] && AUDITD_VAR=${BASH_REMATCH[1]}
    	    echo "${AUDITD_VAR}" >> "${FILE_PATTERN_AUDITD_CONF}"
	done 3< <(grep -E "^[[:space:]]*AUDITD\[" ${SYSCONF}/logging 2>/dev/null)
    elif [[ ${COMMAND} == @("set="|"set+="|"set++=") ]]; then
    	[[ ${AUDITD_NAME} =~ ^.*'['(.*)']' ]] && AUDITD_NAME=${BASH_REMATCH[1]}
    	[[ ${AUDITD_VAR} =~ ^\"(.*)\"$ ]] && AUDITD_VAR=${BASH_REMATCH[1]}
    	echo "${AUDITD_VAR}" >> "${FILE_PATTERN_AUDITD_CONF}"
    elif [[ ${COMMAND} == @("set-="|"set--="|"remove") ]]; then
	if [[ -n ${AUDITD_NAME} && ${AUDITD_VAR} != "" ]]; then
	    [[ ${AUDITD_NAME} =~ ^.*'['(.*)']' ]] && AUDITD_NAME=${BASH_REMATCH[1]}
	    AUDITD_VAR=${AUDITD[${AUDITD_NAME}]}
	fi
	[[ ${AUDITD_VAR} =~ ^\"(.*)\"$ ]] && AUDITD_VAR=${BASH_REMATCH[1]}
	ESC_AUDITD_VAR=$(sed 's/[^a-zA-Z0-9=",_@#%&<> -]/\\&/g' <<< "${AUDITD_VAR}")
    	sed "/^${ESC_AUDITD_VAR}$/d" -i "${FILE_PATTERN_AUDITD_CONF}"
    fi
}

exec_auditd_live(){
    [[ -z ${ROOTFS} ]] || return 0
    SERVICE_NAME="auditd.service"
    if [[ $(pgrep -fc "exec_audit_live") == 1 ]]; then
	if systemctl --quiet is-enabled ${SERVICE_NAME} 2>/dev/null; then
	    sleep 5
	    systemctl --quiet reset-failed ${SERVICE_NAME}
	    systemctl --quiet restart ${SERVICE_NAME} 2>/dev/null
	fi
    fi
}

## Настройка журналов
## https://www.freedesktop.org/software/systemd/man/journald.conf.html
## JOURNALD[<var>]=<value>
##   <var>                      # Имя переменной настройки журнала
##     Storage                  # Указывает, где хранить журнал. Доступны следующие параметры:
##                              # "volatile" - Журнал хранится в оперативной памяти, т.е. в каталоге /run/log/journal
##                              # "persistent" - Данные хранятся на диске, т.е. в каталоге /var/log/journal
##                              # "auto" - используется по-умолчанию
##                              # "none" - Журнал не ведётся
##     Compress                 # Пороговое значение данных для сжатия и записи в ФС. Может принимать yes, no, цифра. Суффиксы, такие как K, M и G
##     SplitMode                # Определяет, следует ли разделять файлы журнала для каждого пользователя: «uid» или «none»
##     RateLimitIntervalSec     # Настраивает ограничение скорости, которое применяется ко всем сообщениям, интервал времени применения =30
##     RateLimitBurst           # Настраивает ограничение скорости, которое применяется ко всем сообщениям, лимит сообщений =10000
##     SystemMaxUse             # Указывает максимальное дисковое пространство, которое может использовать журнал в постоянном хранилище
##     SystemKeepFree           # Указывает объем места, которое журнал должен оставить свободным при добавлении записей журнала в постоянное хранилище
##     SystemMaxFileSize        # Определяет, до какого размера могут вырасти отдельные файлы журнала в постоянном хранилище перед ротацией
##     RuntimeMaxUse            # Указывает максимальное дисковое пространство, которое можно использовать в энергозависимом хранилище (в файловой системе /run)
##     RuntimeKeepFree          # Указывает объем пространства, которое будет выделено для других целей при записи данных в энергозависимое хранилище (в файловой системе /run)
##     RuntimeMaxFileSize       # Указывает объем места, которое отдельный файл журнала может занимать в энергозависимом хранилище (в файловой системе /run) перед ротацией
##     ForwardToConsole         # Перенаправить журнал на консоль, yes|no
##     TTYPath                  # Измените используемый консольный TTY, если используется ForwardToConsole=yes. По умолчанию /dev/console.
##     MaxLevelConsole          # Тип сообщений перенаправляемые в журнал, варианты: emerg|alert|crit|err|warning|notice|info|debug или целочисленные значения в диапазоне 0–7
##   <value>                    # Значение переменной настройки журнала
#JOURNALD[Storage]=persistent
#JOURNALD[Compress]=yes
#JOURNALD[SystemMaxUse]=8M
#JOURNALD[RuntimeMaxUse]=8M
exec_journald(){
    [[ $1 == @("set="|"set+="|"set++="|"set-="|"set--="|"remove") ]] && COMMAND=$1 && shift
    [[ -n ${COMMAND} ]] || COMMAND="set="
    FILE_JOURNALD_CONF="${ROOTFS}/etc/systemd/journald.conf.d/00-ubconfig.conf"
    local PARAM="$@"
    if [[ -n ${PARAM} ]]; then
	local JOURNALD=
	declare -A JOURNALD
	[[ ${PARAM%%=*} =~ [!\$%\&()*+,/\;\<\=\>?\^\{|\}~] ]] || eval "${PARAM%%=*}=\${PARAM#*=}"
    fi
    [[ ! -f ${FILE_JOURNALD_CONF} ]] && mkdir -p "${FILE_JOURNALD_CONF%/*}" && touch ${FILE_JOURNALD_CONF}
    [[ $(cat ${FILE_JOURNALD_CONF}) =~ "[Journal]" ]] || echo "[Journal]" > ${FILE_JOURNALD_CONF}
    if [[ ${COMMAND} == @("set="|"set+="|"set++=") ]] && [[ ${#JOURNALD[@]} != 0 ]]; then
        while IFS= read -u3 NAME_VAR; do
    	    if [[ $(cat ${FILE_JOURNALD_CONF}) =~ "${NAME_VAR}=" ]]; then
    		sed "s/^${NAME_VAR}=.*/${NAME_VAR}=${JOURNALD[${NAME_VAR}]}/g" -i ${FILE_JOURNALD_CONF}
    	    else
    		echo "${NAME_VAR}=${JOURNALD[${NAME_VAR}]}" >> ${FILE_JOURNALD_CONF}
    	    fi
	done 3< <(printf "%s\n" "${!JOURNALD[@]}")
    elif [[ ${COMMAND} == @("set-="|"set--="|"remove") ]]; then
	if [[ ${PARAM%%=*} =~ ^.*'['(.*)']' ]]; then
	    NAME_VAR=${BASH_REMATCH[1]}
	    sed "/${NAME_VAR}=/d" -i ${FILE_JOURNALD_CONF}
	fi
    fi
}

exec_journald_live(){
    [[ -z ${ROOTFS} ]] || return 0
    SERVICE_NAME="systemd-journald.service"
    if [[ $(pgrep -fc "exec_journald_live") == 1 ]]; then
	sleep 5
	systemctl reset-failed ${SERVICE_NAME}
	systemctl --quiet restart ${SERVICE_NAME}
    fi
}

## Настройка дампа ядра
## https://www.freedesktop.org/software/systemd/man/latest/systemd-coredump.html
## SYSTEMD_COREDUMP[<var>]=<value>
##   <var>                      # Имя переменной настройки журнала
##     Storage                  # Указывает, где хранить дамп ядра. Доступны следующие параметры:
##       journal                # Дампы будут храниться в журнале и перезаписываться в соответствии со стандартными принципами ротации журналов
##       external               # Значение по умолчанию, дампы сохраняются в каталоге /var/lib/systemd/coredump/
##       none                   # Дампы ядра записываются (включая обратную трассировку, если возможно), но не сохраняются
##     Compress                 # Используется для сжатия дампов ядра. Принимает логический аргумент - *yes или no
##      *yes                    # Дампы сжимаются при сохранении (значение по умолчанию)
##       no                     # Дампы сохраняются без сжатия.
##     ProcessSizeMax           # Максимальный размер дампа процесса, который может быть сохранен. Если размер дампа превышает значение этого параметра, то дамп сохранен не будет. Суфиксы: B
##                              # Параметры Storage=none и ProcessSizeMax=0 отключает всю обработку дампа памяти, за исключением записи журнала
##     ExternalSizeMax          # Максимальный (сжатый или несжатый) размер дампа процесса, который будет сохранен на диске
##     JournalSizeMax           # Максимальный (сжатый или несжатый) размер дампа процесса, который будет сохранен в журнале системы
##     MaxUse                   # Максимальный объем общего дискового пространства, который может быть использован для хранения дампов процессов. Суфиксы: B,K,M,G,T,P,E
##     KeepFree                 # Минимальный объем свободного дискового пространства, который должен оставаться на диске при хранении дампов. Суфиксы: B,K,M,G,T,P,E
##   <value>                    # Значение переменной настройки журнала
#SYSTEMD_COREDUMP[Storage]=none
#SYSTEMD_COREDUMP[ProcessSizeMax]=0
exec_systemd_coredump(){
    [[ $1 == @("set="|"set+="|"set++="|"set-="|"set--="|"remove") ]] && COMMAND=$1 && shift
    [[ -n ${COMMAND} ]] || COMMAND="set="
    FILE_SYSTEMD_COREDUMP_CONF="${ROOTFS}/etc/systemd/coredump.conf.d/00-ubconfig.conf"
    local PARAM="$@"
    if [[ -n ${PARAM} ]]; then
	local SYSTEMD_COREDUMP=
	declare -A SYSTEMD_COREDUMP
	[[ ${PARAM%%=*} =~ [!\$%\&()*+,/\;\<\=\>?\^\{|\}~] ]] || eval "${PARAM%%=*}=\${PARAM#*=}"
    fi
    [[ ! -f ${FILE_SYSTEMD_COREDUMP_CONF} ]] && mkdir -p "${FILE_SYSTEMD_COREDUMP_CONF%/*}" && touch ${FILE_SYSTEMD_COREDUMP_CONF}
    [[ $(cat ${FILE_SYSTEMD_COREDUMP_CONF}) =~ "[Coredump]" ]] || echo "[Coredump]" > ${FILE_SYSTEMD_COREDUMP_CONF}
    if [[ ${COMMAND} == @("set="|"set+="|"set++=") ]] && [[ ${#SYSTEMD_COREDUMP[@]} != 0 ]]; then
        while IFS= read -u3 NAME_VAR; do
    	    if [[ $(cat ${FILE_SYSTEMD_COREDUMP_CONF}) =~ "${NAME_VAR}=" ]]; then
    		sed "s/^${NAME_VAR}=.*/${NAME_VAR}=${SYSTEMD_COREDUMP[${NAME_VAR}]}/g" -i ${FILE_SYSTEMD_COREDUMP_CONF}
    	    else
    		echo "${NAME_VAR}=${SYSTEMD_COREDUMP[${NAME_VAR}]}" >> ${FILE_SYSTEMD_COREDUMP_CONF}
    	    fi
	done 3< <(printf "%s\n" "${!SYSTEMD_COREDUMP[@]}")
    elif [[ ${COMMAND} == @("set-="|"set--="|"remove") ]]; then
	if [[ ${PARAM%%=*} =~ ^.*'['(.*)']' ]]; then
	    NAME_VAR=${BASH_REMATCH[1]}
	    sed "/${NAME_VAR}=/d" -i ${FILE_SYSTEMD_COREDUMP_CONF}
	fi
    fi
}

## Настройка ротации файлов логов утилитой logrotate
## https://man.archlinux.org/man/logrotate.conf.5
## LOGROTATE[<tag>]="<mask_file_1>,<mask_file_2>,<mask_file_n>:<setting_1>,<setting_2>,<setting_n>"
#LOGROTATE[samba]="/var/log/samba/log.smbd,/var/log/samba/log.nmbd,/var/log/samba/*.log:notifempty,missingok,copytruncate"
#LOGROTATE[chrony]="/var/log/chrony/*.log:missingok,nocreate,sharedscripts,postrotate,/usr/bin/chronyc cyclelogs > /dev/null 2>&1 || true,endscript"
#LOGROTATE[clamav]="/var/log/clamav/clamd.log,/var/log/clamav/freshclam.log,/var/log/clamav/clamav-milter.log:create 644 clamav clamav,sharedscripts,missingok,notifempty,postrotate,/bin/kill -HUP \`cat /run/clamav/clamd.pid 2>/dev/null\` 2> /dev/null || true,/bin/kill -HUP \`cat /run/clamav/freshclam.pid 2>/dev/null\` 2> /dev/null || true,/bin/kill -HUP \`cat /run/clamav/clamav-milter.pid 2>/dev/null\` 2> /dev/null || true,endscript"
exec_logrotate(){
    [[ $1 == @("set="|"set+="|"set++="|"set-="|"set--="|"remove") ]] && COMMAND=$1 && shift
    [[ -n ${COMMAND} ]] || COMMAND="set="
    FILE_PATTERN_LOGROTATE_CONF="${ROOTFS}/etc/logrotate.d/ubconfig-"
    local PARAM="$@"
    if [[ -n ${PARAM} ]]; then
	local LOGROTATE=
	declare -A LOGROTATE
	[[ ${PARAM%%=*} =~ [!\$%\&()*+,/\;\<\=\>?\^\{|\}~] ]] || eval "${PARAM%%=*}=\${PARAM#*=}"
    fi
    [[ -d ${FILE_PATTERN_LOGROTATE_CONF%/*} ]] || mkdir -p ${PATH_LOGROTATE_CONF%/*}
    if [[ ${COMMAND} == "set=" ]] && [[ ${#LOGROTATE[@]} != 0 ]]; then
        while IFS= read -u3 NAME_FILE; do
    	    FILES_LOG="${LOGROTATE[${NAME_FILE}]%%:*}"; FILES_LOG="${FILES_LOG//,/ }"
    	    RULES_LOG="${LOGROTATE[${NAME_FILE}]#*:}"
    	    TAB_COUNT='\t'
    	    # Вставляем список файлов логов
    	    echo "${FILES_LOG} {" > "${FILE_PATTERN_LOGROTATE_CONF}${NAME_FILE}"    	    
    	    # Вставляем правила для вращения логов
    	    while IFS= read -r SELECT_RULES_LOG; do
    		[[ ${SELECT_RULES_LOG} == "endscript" ]] && TAB_COUNT='\t'
    		echo -e "${TAB_COUNT}${SELECT_RULES_LOG}" >> "${FILE_PATTERN_LOGROTATE_CONF}${NAME_FILE}"
    		[[ ${SELECT_RULES_LOG} == "postrotate" ]] && TAB_COUNT='\t\t'
    	    done < <(tr ',' '\n' <<< ${RULES_LOG})
    	    echo "}" >> "${FILE_PATTERN_LOGROTATE_CONF}${NAME_FILE}"
	done 3< <(printf "%s\n" "${!LOGROTATE[@]}")
    elif [[ ${COMMAND} == @("set+="|"set++=") ]] && [[ ${#LOGROTATE[@]} != 0 ]]; then
        while IFS= read -u3 NAME_FILE; do
    	    [[ -f ${FILE_PATTERN_LOGROTATE_CONF}${NAME_FILE} ]] || return 0
    	    FILES_LOG="${LOGROTATE[${NAME_FILE}]%%:*}"
    	    RULES_LOG="${LOGROTATE[${NAME_FILE}]#*:}"
    	    [[ ${FILES_LOG} == ${RULES_LOG} ]] && unset RULES_LOG
    	    FILES_LOG="${FILES_LOG//,/ }"
	    # Вставить в список файлов последним
    	    [[ -n ${FILES_LOG} ]] && ESC_FILES_LOG=$(sed 's/[^a-zA-Z0-9=",_@#%&<> -]/\\&/g' <<< "${FILES_LOG}")
    	    [[ -n ${FILES_LOG} ]] && [[ ! $(cat "${FILE_PATTERN_LOGROTATE_CONF}${NAME_FILE}") =~ ${FILES_LOG} ]] \
    	    && sed -E "0,/ \{/s/ \{/ ${ESC_FILES_LOG} \{/" -i "${FILE_PATTERN_LOGROTATE_CONF}${NAME_FILE}"
    	    # Вставить в список правил последним
    	    [[ -n ${RULES_LOG} ]] && ESC_RULES_LOG=$(sed 's/[^a-zA-Z0-9=",_@#%&<> -]/\\&/g' <<< "${RULES_LOG}")
    	    [[ -n ${RULES_LOG} ]] && [[ ! $(cat "${FILE_PATTERN_LOGROTATE_CONF}${NAME_FILE}") =~ ${RULES_LOG} ]] \
    	    && sed -E "/^\}$/i\\\t${ESC_RULES_LOG}" -i "${FILE_PATTERN_LOGROTATE_CONF}${NAME_FILE}"
	done 3< <(printf "%s\n" "${!LOGROTATE[@]}")
    elif [[ ${COMMAND} == @("set-="|"set--=") ]]; then
        while IFS= read -u3 NAME_FILE; do
    	    FILES_LOG="${LOGROTATE[${NAME_FILE}]%%:*}"
    	    RULES_LOG="${LOGROTATE[${NAME_FILE}]#*:}"
    	    [[ ${FILES_LOG} == ${RULES_LOG} ]] && unset RULES_LOG
    	    FILES_LOG="${FILES_LOG//,/ }"
    	    # Если удаляемая часть относится к файлу, то просто вырезать, если к тексту, то удалить строку
    	    [[ -n ${FILES_LOG} ]] && ESC_FILES_LOG=$(sed 's/[^a-zA-Z0-9=",_@#%&<> -]/\\&/g' <<< "${FILES_LOG}")
    	    [[ -n ${FILES_LOG} ]] && [[ $(cat "${FILE_PATTERN_LOGROTATE_CONF}${NAME_FILE}") =~ ${FILES_LOG} ]] \
    	    && sed -E "0,/ \{/s/${ESC_FILES_LOG}//" -i "${FILE_PATTERN_LOGROTATE_CONF}${NAME_FILE}"
    	    [[ -n ${RULES_LOG} ]] && ESC_RULES_LOG=$(sed 's/[^a-zA-Z0-9=",_@#%&<> -]/\\&/g' <<< "${RULES_LOG}")
	    [[ -n ${RULES_LOG} ]] && [[ $(cat "${FILE_PATTERN_LOGROTATE_CONF}${NAME_FILE}") =~ ${RULES_LOG} ]] \
	    && sed "/${ESC_RULES_LOG}/d" -i "${FILE_PATTERN_LOGROTATE_CONF}${NAME_FILE}"
	done 3< <(printf "%s\n" "${!LOGROTATE[@]}")
    elif [[ ${COMMAND} == "remove" ]]; then
        while IFS= read -u3 NAME_FILE; do
    	    [[ -f ${FILE_PATTERN_LOGROTATE_CONF}${NAME_FILE} ]] || return 0
	    rm -f "${FILE_PATTERN_LOGROTATE_CONF}${NAME_FILE}"
	done 3< <(printf "%s\n" "${!LOGROTATE[@]}")
    fi
}

# Применить ротцию принудительно на указанный файл
# $1	# Имя переменной с файлом, пример: LOGROTATE[clamav]
exec_logrotate_live(){
    [[ -z ${ROOTFS} ]] || return 0
    NAME_FILE=$1
    [[ ${NAME_FILE%%=*} =~ ^.*'['(.*)']' ]] && NAME_FILE=${BASH_REMATCH[1]}
    [[ -n ${NAME_FILE} ]] || return 0
    FILE_PATTERN_LOGROTATE_CONF="${ROOTFS}/etc/logrotate.d/ubconfig-"
    if [[ $(pgrep -fc "exec_logrotate_live") == 1 ]]; then
	sleep 2
	logrotate -f "${FILE_PATTERN_LOGROTATE_CONF}${NAME_FILE}"
    fi
}

################
##### MAIN #####
################

    # Если файл подключен как ресурс с функциями, то выйти
    return 0 2>/dev/null && return 0
    if [[ -z $@ ]]; then
        while read -r FUNCTION; do
            $"${FUNCTION##* }"
        done < <(declare -F | grep "declare -f exec_")
    else
	FUNCTION=
	while [[ $# -gt 0 ]]; do
	    [[ -z ${1} ]] || { declare -f ${1} &>/dev/null && FUNCTION+="; ${1}" || FUNCTION+=" '${1}'"; }
	    shift 
	done
	eval ${FUNCTION#*; }
    fi
