diff --git a/install.sh b/install.sh index 4b99053..b34d91c 100755 --- a/install.sh +++ b/install.sh @@ -1,6 +1,6 @@ #!/bin/bash -### version: 1.10.1 +### version: 1.10.2 # ~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.# # Nightscout Mikr.us setup script # @@ -54,8 +54,17 @@ DISK_LOW_MAIL=5184000 # == 60 days in seconds DISK_CRITICAL_WARNING=104857600 # == 100 MiB DISK_CRITICAL_MAIL=604800 # == 7 days in seconds DOCKER_DOWN_MAIL=604800 # == 7 days in seconds -SCRIPT_VERSION="1.10.1" #auto-update -SCRIPT_BUILD_TIME="2026.01.04" #auto-update +SCRIPT_VERSION="1.10.2" #auto-update +SCRIPT_BUILD_TIME="2026.01.05" #auto-update + +#======================================= +# DOWNLOAD CONFIG +#======================================= + +GITHUB_BASE_URL="https://raw.githubusercontent.com/dlvoy/mikrus-installer" +GITEA_BASE_URL="https://gitea.dzienia.pl/shared/mikrus-installer/raw/branch" +GITHUB_UNAVAILABLE="" # Empty string = GitHub is available, set to "1" if GitHub fails + #======================================= # SETUP @@ -122,6 +131,8 @@ if [[ -n "${POSIXLY_CORRECT+1}" ]]; then abort 'Bash must not run in POSIX mode. Please unset POSIXLY_CORRECT and try again.' fi + + #======================================= # FORMATERS #======================================= @@ -177,8 +188,10 @@ uni_leave_logs=" $(printf '\U1F4DC') Zostaw " uni_ns_ok="$(printf '\U1F7E2') działa" uni_watchdog_ok="$(printf '\U1F415') Nightscout działa" + + #======================================= -# UTILS +# CONSOLE OUTPUT UTILS #======================================= shell_join() { @@ -223,6 +236,12 @@ warn() { printf "${tty_red}Warning${tty_reset}: %s\n" "$(chomp "$1")" >&2 } + + +#======================================= +# UTILS +#======================================= + # Search for the given executable in PATH (avoids a dependency on the `which` command) which() { # Alias to Bash built-in command `type -P` @@ -246,7 +265,7 @@ version_lt() { [[ "${1%.*}" -lt "${2%.*}" ]] || [[ "${1%.*}" -eq "${2%.*}" && "${1#*.}" -lt "${2#*.}" ]] } -ifIsSet() { +if_is_set() { [[ ${!1-x} == x ]] && return 1 || return 0 } @@ -256,12 +275,11 @@ exit_on_no_cancel() { fi } -event_mark() { - local eventName=$1 - local eventTime=$(date -u +"%Y-%m-%dT%H:%M:%SZ") - mkdir -p "/srv/nightscout/data" >>"$LOGTO" 2>&1 - dotenv-tool -r -i "${EVENTS_DB}" -m "${eventName}=${eventTime}" -} + + +#======================================= +# STRING UTILS +#======================================= join_by() { local d=${1-} f=${2-} @@ -270,6 +288,225 @@ join_by() { fi } +lpad_text() { + local inText="$1" + local len=${#inText} + local spaces=" " + if ((len == 0)); then + echo "" + else + echo "${spaces:0:$(($2 - len))}$1" + fi +} + +center_text() { + local inText="$1" + local len=${#inText} + local spaces=" " + if ((len == 0)); then + echo "" + else + echo "${spaces:0:$((($2 - len) / 2))}$1" + fi +} + +rpad_text() { + local inText="$1" + local len=${#inText} + local spaces=" " + if ((len == 0)); then + echo "" + else + local padSize=$(($2 - len)) + echo "$1${spaces:0:${padSize}}" + fi +} + +multiline_length() { + local string=$1 + local maxLen=0 + # shellcheck disable=SC2059 + readarray -t array <<<"$(printf "$string")" + for i in "${!array[@]}"; do + local line=${array[i]} + lineLen=${#line} + if [ "$lineLen" -gt "$maxLen" ]; then + maxLen="$lineLen" + fi + done + + echo "$maxLen" +} + +center_multiline() { + local maxLen=70 + local string="$*" + + if [ $# -gt 1 ]; then + maxLen=$1 + shift 1 + string="$*" + else + maxLen=$(multiline_length "$string") + fi + + # shellcheck disable=SC2059 + readarray -t array <<<"$(printf "$string")" + for i in "${!array[@]}"; do + local line=${array[i]} + # shellcheck disable=SC2005 + echo "$(center_text "$line" "$maxLen")" + done +} + +pad_multiline() { + + local string="$*" + local maxLen=$(multiline_length "$string") + + # shellcheck disable=SC2059 + readarray -t array <<<"$(printf "$string")" + for i in "${!array[@]}"; do + local line=${array[i]} + # shellcheck disable=SC2005 + echo "$(rpad_text "$line" "$maxLen")" + done +} + + + +#======================================= +# SCREEN DIALOGS +#======================================= + +echo_progress() { + local realProg=$1 # numerical real progress + local realMax=$2 # max value of that progress + local realStart=$3 # where real progress starts, % + local countr=$4 # real ticker, 3 ticks/s + local firstPhaseSecs=$5 # how long first, ticked part, last + + if [ "$realProg" -eq "0" ]; then + local progrsec=$(((countr * realStart) / (3 * firstPhaseSecs))) + if [ "$progrsec" -lt "$realStart" ]; then + echo "$progrsec" + else + echo "$realStart" + fi + else + echo $(((realProg * (100 - realStart) / realMax) + realStart)) + fi +} + +process_gauge() { + local process_to_measure=$1 + local lenmsg + lenmsg=$(echo "$4" | wc -l) + eval "$process_to_measure" & + local thepid=$! + local num=1 + while true; do + echo 0 + while kill -0 "$thepid" >/dev/null 2>&1; do + eval "$2" "$num" + num=$((num + 1)) + sleep 0.3 + done + echo 100 + break + done | whiptail --title "$3" --gauge "\n $4\n" $((lenmsg + 6)) 70 0 +} + +okdlg() { + local title=$1 + shift 1 + local msg="$*" + local lcount=$(echo -e "$msg" | grep -c '^') + local width=$(multiline_length "$msg") + whiptail --title "$title" --msgbox "$(center_multiline $((width + 4)) "$msg")" $((lcount + 6)) $((width + 9)) +} + +confirmdlg() { + local title=$1 + local btnlabel=$2 + shift 2 + local msg="$*" + local lcount=$(echo -e "$msg" | grep -c '^') + local width=$(multiline_length "$msg") + whiptail --title "$title" --ok-button "$btnlabel" --msgbox "$(center_multiline $((width + 4)) "$msg")" $((lcount + 6)) $((width + 9)) +} + +yesnodlg() { + yesnodlg_base "y" "$@" +} + +noyesdlg() { + yesnodlg_base "n" "$@" +} + +yesnodlg_base() { + local defaultbtn=$1 + local title=$2 + local ybtn=$3 + local nbtn=$4 + shift 4 + local msg="$*" + # shellcheck disable=SC2059 + local linec=$(printf "$msg" | grep -c '^') + local width=$(multiline_length "$msg") + local ylen=${#ybtn} + local nlen=${#nbtn} + # we need space for all < > around buttons + local minbtn=$((ylen + nlen + 6)) + # minimal nice width of dialog + local minlen=$((minbtn > 15 ? minbtn : 15)) + local mwidth=$((minlen > width ? minlen : width)) + + # whiptail has bug, buttons are NOT centered + local rpad=$((width < minbtn ? (nlen - 2) + ((nlen - 2) / 2) : 4)) + local padw=$((mwidth + rpad)) + + if [[ "$defaultbtn" == "y" ]]; then + whiptail --title "$title" --yesno "$(center_multiline "$padw" "$msg")" \ + --yes-button "$ybtn" --no-button "$nbtn" \ + $((linec + 7)) $((padw + 4)) + else + whiptail --title "$title" --yesno --defaultno "$(center_multiline "$padw" "$msg")" \ + --yes-button "$ybtn" --no-button "$nbtn" \ + $((linec + 7)) $((padw + 4)) + fi +} + + + + +#======================================= +# VARIABLES +#======================================= + +packages=() +aptGetWasUpdated=0 +freshInstall=0 +cachedMenuDomain='' +lastTimeSpaceInfo=0 +diagnosticsSizeOk=0 +forceUpdateCheck=0 + +MIKRUS_APIKEY='' +MIKRUS_HOST='' + + +#======================================= +# EVENTS MARKERS LOGIC +#======================================= + +event_mark() { + local eventName=$1 + local eventTime=$(date -u +"%Y-%m-%dT%H:%M:%SZ") + mkdir -p "/srv/nightscout/data" >>"$LOGTO" 2>&1 + dotenv-tool -r -i "${EVENTS_DB}" -m "${eventName}=${eventTime}" +} + event_label() { case $1 in cleanup) @@ -332,17 +569,6 @@ event_label() { esac } -lpad_text() { - local inText="$1" - local len=${#inText} - local spaces=" " - if ((len == 0)); then - echo "" - else - echo "${spaces:0:$(($2 - len))}$1" - fi -} - event_count() { if [ ! -f ${EVENTS_DB} ]; then echo "0" @@ -478,206 +704,8 @@ clear_last_time() { event_mark "last_${actionName}_clear" } -#======================================= -# HELPERS -#======================================= - -echo_progress() { - local realProg=$1 # numerical real progress - local realMax=$2 # max value of that progress - local realStart=$3 # where real progress starts, % - local countr=$4 # real ticker, 3 ticks/s - local firstPhaseSecs=$5 # how long first, ticked part, last - - if [ "$realProg" -eq "0" ]; then - local progrsec=$(((countr * realStart) / (3 * firstPhaseSecs))) - if [ "$progrsec" -lt "$realStart" ]; then - echo "$progrsec" - else - echo "$realStart" - fi - else - echo $(((realProg * (100 - realStart) / realMax) + realStart)) - fi -} - -process_gauge() { - local process_to_measure=$1 - local lenmsg - lenmsg=$(echo "$4" | wc -l) - eval "$process_to_measure" & - local thepid=$! - local num=1 - while true; do - echo 0 - while kill -0 "$thepid" >/dev/null 2>&1; do - eval "$2" "$num" - num=$((num + 1)) - sleep 0.3 - done - echo 100 - break - done | whiptail --title "$3" --gauge "\n $4\n" $((lenmsg + 6)) 70 0 -} - -download_if_not_exists() { - if [[ -f $2 ]]; then - msgok "Found $1" - else - ohai "Downloading $1..." - curl -fsSL -o "$2" "$3" - msgcheck "Downloaded $1" - fi -} -center_text() { - local inText="$1" - local len=${#inText} - local spaces=" " - if ((len == 0)); then - echo "" - else - echo "${spaces:0:$((($2 - len) / 2))}$1" - fi -} -rpad_text() { - local inText="$1" - local len=${#inText} - local spaces=" " - if ((len == 0)); then - echo "" - else - local padSize=$(($2 - len)) - echo "$1${spaces:0:${padSize}}" - fi -} - -multiline_length() { - local string=$1 - local maxLen=0 - # shellcheck disable=SC2059 - readarray -t array <<<"$(printf "$string")" - for i in "${!array[@]}"; do - local line=${array[i]} - lineLen=${#line} - if [ "$lineLen" -gt "$maxLen" ]; then - maxLen="$lineLen" - fi - done - - echo "$maxLen" -} - -center_multiline() { - local maxLen=70 - local string="$*" - - if [ $# -gt 1 ]; then - maxLen=$1 - shift 1 - string="$*" - else - maxLen=$(multiline_length "$string") - fi - - # shellcheck disable=SC2059 - readarray -t array <<<"$(printf "$string")" - for i in "${!array[@]}"; do - local line=${array[i]} - # shellcheck disable=SC2005 - echo "$(center_text "$line" "$maxLen")" - done -} - -pad_multiline() { - - local string="$*" - local maxLen=$(multiline_length "$string") - - # shellcheck disable=SC2059 - readarray -t array <<<"$(printf "$string")" - for i in "${!array[@]}"; do - local line=${array[i]} - # shellcheck disable=SC2005 - echo "$(rpad_text "$line" "$maxLen")" - done -} - -okdlg() { - local title=$1 - shift 1 - local msg="$*" - local lcount=$(echo -e "$msg" | grep -c '^') - local width=$(multiline_length "$msg") - whiptail --title "$title" --msgbox "$(center_multiline $((width + 4)) "$msg")" $((lcount + 6)) $((width + 9)) -} - -confirmdlg() { - local title=$1 - local btnlabel=$2 - shift 2 - local msg="$*" - local lcount=$(echo -e "$msg" | grep -c '^') - local width=$(multiline_length "$msg") - whiptail --title "$title" --ok-button "$btnlabel" --msgbox "$(center_multiline $((width + 4)) "$msg")" $((lcount + 6)) $((width + 9)) -} - -yesnodlg() { - yesnodlg_base "y" "$@" -} - -noyesdlg() { - yesnodlg_base "n" "$@" -} - -yesnodlg_base() { - local defaultbtn=$1 - local title=$2 - local ybtn=$3 - local nbtn=$4 - shift 4 - local msg="$*" - # shellcheck disable=SC2059 - local linec=$(printf "$msg" | grep -c '^') - local width=$(multiline_length "$msg") - local ylen=${#ybtn} - local nlen=${#nbtn} - # we need space for all < > around buttons - local minbtn=$((ylen + nlen + 6)) - # minimal nice width of dialog - local minlen=$((minbtn > 15 ? minbtn : 15)) - local mwidth=$((minlen > width ? minlen : width)) - - # whiptail has bug, buttons are NOT centered - local rpad=$((width < minbtn ? (nlen - 2) + ((nlen - 2) / 2) : 4)) - local padw=$((mwidth + rpad)) - - if [[ "$defaultbtn" == "y" ]]; then - whiptail --title "$title" --yesno "$(center_multiline "$padw" "$msg")" \ - --yes-button "$ybtn" --no-button "$nbtn" \ - $((linec + 7)) $((padw + 4)) - else - whiptail --title "$title" --yesno --defaultno "$(center_multiline "$padw" "$msg")" \ - --yes-button "$ybtn" --no-button "$nbtn" \ - $((linec + 7)) $((padw + 4)) - fi -} - -#======================================= -# VARIABLES -#======================================= - -packages=() -aptGetWasUpdated=0 -freshInstall=0 -cachedMenuDomain='' -lastTimeSpaceInfo=0 -diagnosticsSizeOk=0 -forceUpdateCheck=0 - -MIKRUS_APIKEY='' -MIKRUS_HOST='' #======================================= # ACTIONS AND STEPS @@ -729,6 +757,19 @@ add_if_not_ok_cmd() { fi } +add_if_not_ok_compose() { + local RESULT=$? + if [ "$RESULT" -eq 0 ]; then + msgcheck "$1 installed" + else + ohai "Installing $1..." + mkdir -p "~/.docker/cli-plugins" >> "$LOGTO" 2>&1 + curl -SL "https://github.com/docker/compose/releases/download/v2.29.7/docker-compose-linux-x86_64" -o "~/.docker/cli-plugins/docker-compose" >> "$LOGTO" 2>&1 + chmod +x "~/.docker/cli-plugins/docker-compose" >> "$LOGTO" 2>&1 + msgcheck "Installing $1 successfull" + fi +} + check_git() { git --version >/dev/null 2>&1 add_if_not_ok "GIT" "git" @@ -740,8 +781,8 @@ check_docker() { } check_docker_compose() { - docker compose -v >/dev/null 2>&1 - add_if_not_ok "Docker compose" "docker-compose" + docker compose version >/dev/null 2>&1 + add_if_not_ok_compose "Docker compose" } patch_docker_compose() { @@ -906,7 +947,7 @@ setup_security() { setup_packages() { # shellcheck disable=SC2145 # shellcheck disable=SC2068 - (ifIsSet packages && setup_update_repo && + (if_is_set packages && setup_update_repo && ohai "Installing packages: ${packages[@]}" && apt-get -yq install ${packages[@]} >>"$LOGTO" 2>&1 && msgcheck "Install successfull") || msgok "All required packages already installed" @@ -1086,6 +1127,16 @@ source_admin() { fi } +download_if_not_exists() { + if [[ -f $2 ]]; then + msgok "Found $1" + else + ohai "Downloading $1..." + curl -fsSL -o "$2" "$3" + msgcheck "Downloaded $1" + fi +} + download_conf() { download_if_not_exists "deployment config" "$ENV_FILE_DEP" "https://gitea.dzienia.pl/shared/mikrus-installer/raw/branch/$UPDATE_CHANNEL/templates/deployment.env" download_if_not_exists "nightscout config" "$ENV_FILE_NS" "https://gitea.dzienia.pl/shared/mikrus-installer/raw/branch/$UPDATE_CHANNEL/templates/nightscout.env" @@ -3029,6 +3080,8 @@ parse_commandline_args() { } + + #======================================= # MAIN SCRIPT #======================================= diff --git a/package.json b/package.json index 1e30480..80371a8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@dlvoy/ns-installer-mikrus", - "version": "1.10.1", + "version": "1.10.2", "description": "Nightscout installer for mikr.us VPS", "main": "index.js", "scripts": { @@ -8,4 +8,4 @@ }, "author": "Dominik Dzienia ", "license": "CC-BY-NC-ND-4.0" -} +} \ No newline at end of file diff --git a/src/lib.sh b/src/lib.sh index e27ae9d..b33446a 100644 --- a/src/lib.sh +++ b/src/lib.sh @@ -49,612 +49,28 @@ GITHUB_BASE_URL="https://raw.githubusercontent.com/dlvoy/mikrus-installer" GITEA_BASE_URL="https://gitea.dzienia.pl/shared/mikrus-installer/raw/branch" GITHUB_UNAVAILABLE="" # Empty string = GitHub is available, set to "1" if GitHub fails +#dev-begin #======================================= -# SETUP +# IMPORTS - generic #======================================= -set -u - -abort() { - printf "%s\n" "$@" >&2 - exit 1 -} - -export NEWT_COLORS=' - root=white,black - border=black,lightgray - window=lightgray,lightgray - shadow=black,gray - title=black,lightgray - button=black,cyan - actbutton=white,cyan - compactbutton=black,lightgray - checkbox=black,lightgray - actcheckbox=lightgray,cyan - entry=black,lightgray - disentry=gray,lightgray - label=black,lightgray - listbox=black,lightgray - actlistbox=black,cyan - sellistbox=lightgray,black - actsellistbox=lightgray,black - textbox=black,lightgray - acttextbox=black,cyan - emptyscale=,gray - fullscale=,cyan - helpline=white,black - roottext=lightgrey,black -' - -#======================================= -# SANITY CHECKS -#======================================= - -# Fail fast with a concise message when not using bash -# Single brackets are needed here for POSIX compatibility -# shellcheck disable=SC2292 -if [ -z "${BASH_VERSION:-}" ]; then - abort "Bash is required to interpret this script." -fi - -# Check if script is run with force-interactive mode in CI -if [[ -n "${CI-}" && -n "${INTERACTIVE-}" ]]; then - abort "Cannot run force-interactive mode in CI." -fi - -# Check if both `INTERACTIVE` and `NONINTERACTIVE` are set -# Always use single-quoted strings with `exp` expressions -# shellcheck disable=SC2016 -if [[ -n "${INTERACTIVE-}" && -n "${NONINTERACTIVE-}" ]]; then - abort 'Both `$INTERACTIVE` and `$NONINTERACTIVE` are set. Please unset at least one variable and try again.' -fi - -# Check if script is run in POSIX mode -if [[ -n "${POSIXLY_CORRECT+1}" ]]; then - abort 'Bash must not run in POSIX mode. Please unset POSIXLY_CORRECT and try again.' -fi - -#======================================= -# FORMATERS -#======================================= - -if [[ -t 1 ]]; then - tty_escape() { printf "\033[%sm" "$1"; } -else - tty_escape() { :; } -fi -tty_mkbold() { tty_escape "1;$1"; } -# tty_underline="$(tty_escape "4;39")" -tty_blue="$(tty_mkbold 34)" -tty_red="$(tty_mkbold 31)" -tty_bold="$(tty_mkbold 39)" -tty_reset="$(tty_escape 0)" - -NL="\n" -TL="\n\n" - -#======================================= -# EMOJIS -#======================================= - -emoji_check="\U2705" -emoji_ok="\U1F197" -emoji_err="\U274C" -emoji_note="\U1F4A1" - -uni_bullet=" $(printf '\u2022') " -uni_copyright="$(printf '\uA9\uFE0F')" -uni_bullet_pad=" " -uni_warn="$(printf "\U26A0")" - -uni_exit=" $(printf '\U274C') Wyjdź " -uni_start=" $(printf '\U1F984') Zaczynamy " -uni_menu=" $(printf '\U1F6E0') Menu " -uni_finish=" $(printf '\U1F984') Zamknij " -uni_reenter=" $(printf '\U21AA') Tak " -uni_noenter=" $(printf '\U2716') Nie " -uni_back=" $(printf '\U2B05') Wróć " -uni_select=" Wybierz " -uni_excl="$(printf '\U203C')" -uni_confirm_del=" $(printf '\U1F4A3') Tak " -uni_confirm_ch=" $(printf '\U1F199') Zmień " -uni_confirm_upd=" $(printf '\U1F199') Aktualizuj " -uni_confirm_ed=" $(printf '\U1F4DD') Edytuj " -uni_install=" $(printf '\U1F680') Instaluj " -uni_resign=" $(printf '\U1F6AB') Rezygnuję " -uni_send=" $(printf '\U1F4E7') Wyślij " -uni_delete=" $(printf '\U1F5D1') Usuń " -uni_leave_logs=" $(printf '\U1F4DC') Zostaw " - -uni_ns_ok="$(printf '\U1F7E2') działa" -uni_watchdog_ok="$(printf '\U1F415') Nightscout działa" - -#======================================= -# UTILS -#======================================= - -shell_join() { - local arg - printf "%s" "$1" - shift - for arg in "$@"; do - printf " " - printf "%s" "${arg// /\ }" - done -} - -chomp() { - printf "%s" "${1/"$'\n'"/}" -} - -ohai() { - printf "${tty_blue}==>${tty_bold} %s${tty_reset}\n" "$(shell_join "$@")" -} - -msgok() { - # shellcheck disable=SC2059 - printf "$emoji_ok $1\n" -} - -msgnote() { - # shellcheck disable=SC2059 - printf "$emoji_note $1\n" -} - -msgcheck() { - # shellcheck disable=SC2059 - printf "$emoji_check $1\n" -} - -msgerr() { - # shellcheck disable=SC2059 - printf "$emoji_err $1\n" -} - -warn() { - printf "${tty_red}Warning${tty_reset}: %s\n" "$(chomp "$1")" >&2 -} - -# Search for the given executable in PATH (avoids a dependency on the `which` command) -which() { - # Alias to Bash built-in command `type -P` - type -P "$@" -} - -major_minor() { - echo "${1%%.*}.$( - x="${1#*.}" - echo "${x%%.*}" - )" -} - -version_gt() { - [[ "${1%.*}" -gt "${2%.*}" ]] || [[ "${1%.*}" -eq "${2%.*}" && "${1#*.}" -gt "${2#*.}" ]] -} -version_ge() { - [[ "${1%.*}" -gt "${2%.*}" ]] || [[ "${1%.*}" -eq "${2%.*}" && "${1#*.}" -ge "${2#*.}" ]] -} -version_lt() { - [[ "${1%.*}" -lt "${2%.*}" ]] || [[ "${1%.*}" -eq "${2%.*}" && "${1#*.}" -lt "${2#*.}" ]] -} - -ifIsSet() { - [[ ${!1-x} == x ]] && return 1 || return 0 -} - -exit_on_no_cancel() { - if [ $? -eq 1 ]; then - exit 0 - fi -} - -event_mark() { - local eventName=$1 - local eventTime=$(date -u +"%Y-%m-%dT%H:%M:%SZ") - mkdir -p "/srv/nightscout/data" >>"$LOGTO" 2>&1 - dotenv-tool -r -i "${EVENTS_DB}" -m "${eventName}=${eventTime}" -} - -join_by() { - local d=${1-} f=${2-} - if shift 2; then - printf %s "$f" "${@/#/$d}" - fi -} - -event_label() { - case $1 in - cleanup) - echo "Czyszczenie" - ;; - install) - echo "Instalacja" - ;; - update_system) - echo "Aktualizacja systemu" - ;; - update_tool) - echo "Aktualizacja narzędzia" - ;; - update_containers) - echo "Aktualizacja kontenerów" - ;; - uninstall) - echo "Odinstalowanie" - ;; - remove_containers) - echo "Usunięcie kontenerów" - ;; - remove_db_data) - echo "Usunięcie danych bazy" - ;; - remove_all_data) - echo "Usunięcie danych" - ;; - change_ns_version) - echo "Zmiana wersji Nightscout" - ;; - edit_env_manual) - echo "Edycja konfiguracji" - ;; - restart_both) - echo "Wymuszony restart NS+DB" - ;; - restart_ns) - echo "Wymuszony restart NS" - ;; - last_disk_warning) - echo "Brak miejsca" - ;; - last_disk_critical) - echo "Krytyczny brak miejsca" - ;; - last_docker_down) - echo "Awaria Dockera" - ;; - last_server_restart_needed) - echo "Potrzebny restart serwera" - ;; - last_update_needed) - echo "Potrzebna aktualizacja" - ;; - *) - echo "$1" - ;; - esac -} - -lpad_text() { - local inText="$1" - local len=${#inText} - local spaces=" " - if ((len == 0)); then - echo "" - else - echo "${spaces:0:$(($2 - len))}$1" - fi -} - -event_count() { - if [ ! -f ${EVENTS_DB} ]; then - echo "0" - else - local eventsJSON=$(dotenv-tool parse -r -f "${EVENTS_DB}") - local eventsKeysStr=$(echo "${eventsJSON}" | jq -r ".values | keys[]") - local eventsCount=${#eventsKeysStr} - if ((eventsCount > 0)); then - mapfile -t eventList < <(echo "${eventsKeysStr}") - echo "${#eventList[@]}" - else - echo "0" - fi - fi -} - -event_list() { - if [ ! -f ${EVENTS_DB} ]; then - echo "Nie odnotowano zdarzeń" - else - local eventsJSON=$(dotenv-tool parse -r -f "${EVENTS_DB}") - local eventsKeysStr=$(echo "${eventsJSON}" | jq -r ".values | keys[]") - local eventsCount=${#eventsKeysStr} - - if ((eventsCount > 0)); then - mapfile -t eventList < <(echo "${eventsKeysStr}") - - local namesTab=() - local labelsTab=() - local valuesTab=() - for eventId in "${eventList[@]}"; do - mapfile -t -d '_' eventIdSplit <<<"${eventId}" - local eventTail=$(echo "${eventIdSplit[-1]}" | tr -d '\n') - unset "eventIdSplit[-1]" - printf -v eventBase '%s_' "${eventIdSplit[@]}" - local eventName="${eventBase%_}" - if [ ${#eventIdSplit[@]} -eq 0 ]; then - eventName="$eventTail" - eventTail="" - fi - - if [[ "$eventTail" == "start" ]] || [[ "$eventTail" == "end" ]]; then - if [[ ! " ${namesTab[*]} " =~ [[:space:]]${eventName}[[:space:]] ]]; then - namesTab+=("${eventName}") - local startVar=$(echo "$eventsJSON" | jq -r ".values.${eventName}_start") - local endVar=$(echo "$eventsJSON" | jq -r ".values.${eventName}_end") - local joinedVar="od: $startVar do: $endVar" - local fixedVar=$(echo "$joinedVar" | sed -E -e "s/ ?(od|do): null ?//g") - if [[ "$fixedVar" =~ od: ]] && [[ "$fixedVar" =~ do: ]]; then - fixedVar=$(echo "$fixedVar" | sed -E -e "s/do:/\ndo:/g") - fi - fixedVar=$(echo "$fixedVar" | sed -E -e "s/od:/🕓/g") - fixedVar=$(echo "$fixedVar" | sed -E -e "s/do:/✅/g") - valuesTab+=("$fixedVar") - fi - else - if [[ "$eventTail" == "set" ]] || [[ "$eventTail" == "clear" ]]; then - if [[ ! " ${namesTab[*]} " =~ [[:space:]]${eventName}[[:space:]] ]]; then - namesTab+=("${eventName}") - local startVar=$(echo "$eventsJSON" | jq -r ".values.${eventName}_set") - local endVar=$(echo "$eventsJSON" | jq -r ".values.${eventName}_clear") - local joinedVar="od: $startVar zdjęto: $endVar" - local fixedVar=$(echo "$joinedVar" | sed -E -e "s/ ?(od|zdjęto): null ?//g") - if [[ "$fixedVar" =~ od: ]] && [[ "$fixedVar" =~ zdjęto: ]]; then - fixedVar=$(echo "$fixedVar" | sed -E -e "s/zdjęto:/\nzdjęto:/g") - fi - fixedVar=$(echo "$fixedVar" | sed -E -e "s/od:/🚩/g") - fixedVar=$(echo "$fixedVar" | sed -E -e "s/zdjęto:/🏁/g") - valuesTab+=("$fixedVar") - fi - else - namesTab+=("${eventId}") - local exactVar=$(echo "$eventsJSON" | jq -r ".values.${eventId}") - valuesTab+=("🕓 $exactVar") - fi - fi - done - - local maxLen=0 - - for ((i = 0; i < ${#namesTab[@]}; i++)); do - local eventLab="$(event_label "${namesTab[$i]}")" - local labelLen=${#eventLab} - maxLen=$((labelLen > maxLen ? labelLen : maxLen)) - labelsTab+=("$eventLab") - done - - maxLen=$((maxLen + 1)) - - for ((i = 0; i < ${#namesTab[@]}; i++)); do - mapfile -t valuesLines <<<"${valuesTab[$i]}" - local linesCount=${#valuesLines[@]} - if ((linesCount > 1)); then - local spaces=" " - echo "$(lpad_text "${labelsTab[$i]}" "$maxLen") = ${valuesLines[0]}" - for ((l = 1; l < linesCount; l++)); do - echo "${spaces:0:$((maxLen + 3))}${valuesLines[l]}" - done - else - echo "$(lpad_text "${labelsTab[$i]}" "$maxLen") = ${valuesTab[$i]}" - fi - done - else - echo "Nie odnotowano zdarzeń" - fi - fi -} - -get_since_last_time() { - local actionName=$1 - local actionFile="${DATA_ROOT_DIR}/last_${actionName}" - if [ -f "$actionFile" ]; then - local actionLast="$(<"$actionFile")" - local nowDate="$(date +'%s')" - echo $((nowDate - actionLast)) - else - echo -1 - fi -} - -set_last_time() { - local actionName=$1 - local actionFile="${DATA_ROOT_DIR}/last_${actionName}" - local nowDate="$(date +'%s')" - echo "$nowDate" >"$actionFile" - event_mark "last_${actionName}_set" -} - -clear_last_time() { - local actionName=$1 - local actionFile="${DATA_ROOT_DIR}/last_${actionName}" - rm -f "$actionFile" - event_mark "last_${actionName}_clear" -} - -#======================================= -# HELPERS -#======================================= - -echo_progress() { - local realProg=$1 # numerical real progress - local realMax=$2 # max value of that progress - local realStart=$3 # where real progress starts, % - local countr=$4 # real ticker, 3 ticks/s - local firstPhaseSecs=$5 # how long first, ticked part, last - - if [ "$realProg" -eq "0" ]; then - local progrsec=$(((countr * realStart) / (3 * firstPhaseSecs))) - if [ "$progrsec" -lt "$realStart" ]; then - echo "$progrsec" - else - echo "$realStart" - fi - else - echo $(((realProg * (100 - realStart) / realMax) + realStart)) - fi -} - -process_gauge() { - local process_to_measure=$1 - local lenmsg - lenmsg=$(echo "$4" | wc -l) - eval "$process_to_measure" & - local thepid=$! - local num=1 - while true; do - echo 0 - while kill -0 "$thepid" >/dev/null 2>&1; do - eval "$2" "$num" - num=$((num + 1)) - sleep 0.3 - done - echo 100 - break - done | whiptail --title "$3" --gauge "\n $4\n" $((lenmsg + 6)) 70 0 -} - -download_if_not_exists() { - if [[ -f $2 ]]; then - msgok "Found $1" - else - ohai "Downloading $1..." - curl -fsSL -o "$2" "$3" - msgcheck "Downloaded $1" - fi -} - -center_text() { - local inText="$1" - local len=${#inText} - local spaces=" " - if ((len == 0)); then - echo "" - else - echo "${spaces:0:$((($2 - len) / 2))}$1" - fi -} - -rpad_text() { - local inText="$1" - local len=${#inText} - local spaces=" " - if ((len == 0)); then - echo "" - else - local padSize=$(($2 - len)) - echo "$1${spaces:0:${padSize}}" - fi -} - -multiline_length() { - local string=$1 - local maxLen=0 - # shellcheck disable=SC2059 - readarray -t array <<<"$(printf "$string")" - for i in "${!array[@]}"; do - local line=${array[i]} - lineLen=${#line} - if [ "$lineLen" -gt "$maxLen" ]; then - maxLen="$lineLen" - fi - done - - echo "$maxLen" -} - -center_multiline() { - local maxLen=70 - local string="$*" - - if [ $# -gt 1 ]; then - maxLen=$1 - shift 1 - string="$*" - else - maxLen=$(multiline_length "$string") - fi - - # shellcheck disable=SC2059 - readarray -t array <<<"$(printf "$string")" - for i in "${!array[@]}"; do - local line=${array[i]} - # shellcheck disable=SC2005 - echo "$(center_text "$line" "$maxLen")" - done -} - -pad_multiline() { - - local string="$*" - local maxLen=$(multiline_length "$string") - - # shellcheck disable=SC2059 - readarray -t array <<<"$(printf "$string")" - for i in "${!array[@]}"; do - local line=${array[i]} - # shellcheck disable=SC2005 - echo "$(rpad_text "$line" "$maxLen")" - done -} - -okdlg() { - local title=$1 - shift 1 - local msg="$*" - local lcount=$(echo -e "$msg" | grep -c '^') - local width=$(multiline_length "$msg") - whiptail --title "$title" --msgbox "$(center_multiline $((width + 4)) "$msg")" $((lcount + 6)) $((width + 9)) -} - -confirmdlg() { - local title=$1 - local btnlabel=$2 - shift 2 - local msg="$*" - local lcount=$(echo -e "$msg" | grep -c '^') - local width=$(multiline_length "$msg") - whiptail --title "$title" --ok-button "$btnlabel" --msgbox "$(center_multiline $((width + 4)) "$msg")" $((lcount + 6)) $((width + 9)) -} - -yesnodlg() { - yesnodlg_base "y" "$@" -} - -noyesdlg() { - yesnodlg_base "n" "$@" -} - -yesnodlg_base() { - local defaultbtn=$1 - local title=$2 - local ybtn=$3 - local nbtn=$4 - shift 4 - local msg="$*" - # shellcheck disable=SC2059 - local linec=$(printf "$msg" | grep -c '^') - local width=$(multiline_length "$msg") - local ylen=${#ybtn} - local nlen=${#nbtn} - # we need space for all < > around buttons - local minbtn=$((ylen + nlen + 6)) - # minimal nice width of dialog - local minlen=$((minbtn > 15 ? minbtn : 15)) - local mwidth=$((minlen > width ? minlen : width)) - - # whiptail has bug, buttons are NOT centered - local rpad=$((width < minbtn ? (nlen - 2) + ((nlen - 2) / 2) : 4)) - local padw=$((mwidth + rpad)) - - if [[ "$defaultbtn" == "y" ]]; then - whiptail --title "$title" --yesno "$(center_multiline "$padw" "$msg")" \ - --yes-button "$ybtn" --no-button "$nbtn" \ - $((linec + 7)) $((padw + 4)) - else - whiptail --title "$title" --yesno --defaultno "$(center_multiline "$padw" "$msg")" \ - --yes-button "$ybtn" --no-button "$nbtn" \ - $((linec + 7)) $((padw + 4)) - fi -} +DIR="${BASH_SOURCE%/*}" +if [[ ! -d "$DIR" ]]; then DIR="$PWD"; fi +# shellcheck source=/dev/null +. "$DIR/screen_config.sh" +. "$DIR/screen_formaters.sh" +. "$DIR/utils_console.sh" +. "$DIR/utils.sh" +. "$DIR/utils_string.sh" +. "$DIR/screen_dialogs.sh" +#dev-end + +#include screen_config.sh +#include screen_formaters.sh +#include utils_console.sh +#include utils.sh +#include utils_string.sh +#include screen_dialogs.sh #======================================= # VARIABLES @@ -671,6 +87,17 @@ forceUpdateCheck=0 MIKRUS_APIKEY='' MIKRUS_HOST='' +#dev-begin +#======================================= +# IMPORTS - app specific +#======================================= + +# shellcheck source=/dev/null +. "$DIR/logic_events.sh" +#dev-end + +#include logic_events.sh + #======================================= # ACTIONS AND STEPS #======================================= @@ -911,7 +338,7 @@ setup_security() { setup_packages() { # shellcheck disable=SC2145 # shellcheck disable=SC2068 - (ifIsSet packages && setup_update_repo && + (if_is_set packages && setup_update_repo && ohai "Installing packages: ${packages[@]}" && apt-get -yq install ${packages[@]} >>"$LOGTO" 2>&1 && msgcheck "Install successfull") || msgok "All required packages already installed" @@ -1091,6 +518,16 @@ source_admin() { fi } +download_if_not_exists() { + if [[ -f $2 ]]; then + msgok "Found $1" + else + ohai "Downloading $1..." + curl -fsSL -o "$2" "$3" + msgcheck "Downloaded $1" + fi +} + download_conf() { download_if_not_exists "deployment config" "$ENV_FILE_DEP" "https://gitea.dzienia.pl/shared/mikrus-installer/raw/branch/$UPDATE_CHANNEL/templates/deployment.env" download_if_not_exists "nightscout config" "$ENV_FILE_NS" "https://gitea.dzienia.pl/shared/mikrus-installer/raw/branch/$UPDATE_CHANNEL/templates/nightscout.env" diff --git a/src/logic_events.sh b/src/logic_events.sh new file mode 100644 index 0000000..e34f51f --- /dev/null +++ b/src/logic_events.sh @@ -0,0 +1,207 @@ +#======================================= +# EVENTS MARKERS LOGIC +#======================================= + +event_mark() { + local eventName=$1 + local eventTime=$(date -u +"%Y-%m-%dT%H:%M:%SZ") + mkdir -p "/srv/nightscout/data" >>"$LOGTO" 2>&1 + dotenv-tool -r -i "${EVENTS_DB}" -m "${eventName}=${eventTime}" +} + +event_label() { + case $1 in + cleanup) + echo "Czyszczenie" + ;; + install) + echo "Instalacja" + ;; + update_system) + echo "Aktualizacja systemu" + ;; + update_tool) + echo "Aktualizacja narzędzia" + ;; + update_containers) + echo "Aktualizacja kontenerów" + ;; + uninstall) + echo "Odinstalowanie" + ;; + remove_containers) + echo "Usunięcie kontenerów" + ;; + remove_db_data) + echo "Usunięcie danych bazy" + ;; + remove_all_data) + echo "Usunięcie danych" + ;; + change_ns_version) + echo "Zmiana wersji Nightscout" + ;; + edit_env_manual) + echo "Edycja konfiguracji" + ;; + restart_both) + echo "Wymuszony restart NS+DB" + ;; + restart_ns) + echo "Wymuszony restart NS" + ;; + last_disk_warning) + echo "Brak miejsca" + ;; + last_disk_critical) + echo "Krytyczny brak miejsca" + ;; + last_docker_down) + echo "Awaria Dockera" + ;; + last_server_restart_needed) + echo "Potrzebny restart serwera" + ;; + last_update_needed) + echo "Potrzebna aktualizacja" + ;; + *) + echo "$1" + ;; + esac +} + +event_count() { + if [ ! -f ${EVENTS_DB} ]; then + echo "0" + else + local eventsJSON=$(dotenv-tool parse -r -f "${EVENTS_DB}") + local eventsKeysStr=$(echo "${eventsJSON}" | jq -r ".values | keys[]") + local eventsCount=${#eventsKeysStr} + if ((eventsCount > 0)); then + mapfile -t eventList < <(echo "${eventsKeysStr}") + echo "${#eventList[@]}" + else + echo "0" + fi + fi +} + +event_list() { + if [ ! -f ${EVENTS_DB} ]; then + echo "Nie odnotowano zdarzeń" + else + local eventsJSON=$(dotenv-tool parse -r -f "${EVENTS_DB}") + local eventsKeysStr=$(echo "${eventsJSON}" | jq -r ".values | keys[]") + local eventsCount=${#eventsKeysStr} + + if ((eventsCount > 0)); then + mapfile -t eventList < <(echo "${eventsKeysStr}") + + local namesTab=() + local labelsTab=() + local valuesTab=() + for eventId in "${eventList[@]}"; do + mapfile -t -d '_' eventIdSplit <<<"${eventId}" + local eventTail=$(echo "${eventIdSplit[-1]}" | tr -d '\n') + unset "eventIdSplit[-1]" + printf -v eventBase '%s_' "${eventIdSplit[@]}" + local eventName="${eventBase%_}" + if [ ${#eventIdSplit[@]} -eq 0 ]; then + eventName="$eventTail" + eventTail="" + fi + + if [[ "$eventTail" == "start" ]] || [[ "$eventTail" == "end" ]]; then + if [[ ! " ${namesTab[*]} " =~ [[:space:]]${eventName}[[:space:]] ]]; then + namesTab+=("${eventName}") + local startVar=$(echo "$eventsJSON" | jq -r ".values.${eventName}_start") + local endVar=$(echo "$eventsJSON" | jq -r ".values.${eventName}_end") + local joinedVar="od: $startVar do: $endVar" + local fixedVar=$(echo "$joinedVar" | sed -E -e "s/ ?(od|do): null ?//g") + if [[ "$fixedVar" =~ od: ]] && [[ "$fixedVar" =~ do: ]]; then + fixedVar=$(echo "$fixedVar" | sed -E -e "s/do:/\ndo:/g") + fi + fixedVar=$(echo "$fixedVar" | sed -E -e "s/od:/🕓/g") + fixedVar=$(echo "$fixedVar" | sed -E -e "s/do:/✅/g") + valuesTab+=("$fixedVar") + fi + else + if [[ "$eventTail" == "set" ]] || [[ "$eventTail" == "clear" ]]; then + if [[ ! " ${namesTab[*]} " =~ [[:space:]]${eventName}[[:space:]] ]]; then + namesTab+=("${eventName}") + local startVar=$(echo "$eventsJSON" | jq -r ".values.${eventName}_set") + local endVar=$(echo "$eventsJSON" | jq -r ".values.${eventName}_clear") + local joinedVar="od: $startVar zdjęto: $endVar" + local fixedVar=$(echo "$joinedVar" | sed -E -e "s/ ?(od|zdjęto): null ?//g") + if [[ "$fixedVar" =~ od: ]] && [[ "$fixedVar" =~ zdjęto: ]]; then + fixedVar=$(echo "$fixedVar" | sed -E -e "s/zdjęto:/\nzdjęto:/g") + fi + fixedVar=$(echo "$fixedVar" | sed -E -e "s/od:/🚩/g") + fixedVar=$(echo "$fixedVar" | sed -E -e "s/zdjęto:/🏁/g") + valuesTab+=("$fixedVar") + fi + else + namesTab+=("${eventId}") + local exactVar=$(echo "$eventsJSON" | jq -r ".values.${eventId}") + valuesTab+=("🕓 $exactVar") + fi + fi + done + + local maxLen=0 + + for ((i = 0; i < ${#namesTab[@]}; i++)); do + local eventLab="$(event_label "${namesTab[$i]}")" + local labelLen=${#eventLab} + maxLen=$((labelLen > maxLen ? labelLen : maxLen)) + labelsTab+=("$eventLab") + done + + maxLen=$((maxLen + 1)) + + for ((i = 0; i < ${#namesTab[@]}; i++)); do + mapfile -t valuesLines <<<"${valuesTab[$i]}" + local linesCount=${#valuesLines[@]} + if ((linesCount > 1)); then + local spaces=" " + echo "$(lpad_text "${labelsTab[$i]}" "$maxLen") = ${valuesLines[0]}" + for ((l = 1; l < linesCount; l++)); do + echo "${spaces:0:$((maxLen + 3))}${valuesLines[l]}" + done + else + echo "$(lpad_text "${labelsTab[$i]}" "$maxLen") = ${valuesTab[$i]}" + fi + done + else + echo "Nie odnotowano zdarzeń" + fi + fi +} + +get_since_last_time() { + local actionName=$1 + local actionFile="${DATA_ROOT_DIR}/last_${actionName}" + if [ -f "$actionFile" ]; then + local actionLast="$(<"$actionFile")" + local nowDate="$(date +'%s')" + echo $((nowDate - actionLast)) + else + echo -1 + fi +} + +set_last_time() { + local actionName=$1 + local actionFile="${DATA_ROOT_DIR}/last_${actionName}" + local nowDate="$(date +'%s')" + echo "$nowDate" >"$actionFile" + event_mark "last_${actionName}_set" +} + +clear_last_time() { + local actionName=$1 + local actionFile="${DATA_ROOT_DIR}/last_${actionName}" + rm -f "$actionFile" + event_mark "last_${actionName}_clear" +} diff --git a/src/screen_config.sh b/src/screen_config.sh new file mode 100644 index 0000000..f6de931 --- /dev/null +++ b/src/screen_config.sh @@ -0,0 +1,64 @@ +#======================================= +# SETUP +#======================================= + +set -u + +abort() { + printf "%s\n" "$@" >&2 + exit 1 +} + +export NEWT_COLORS=' + root=white,black + border=black,lightgray + window=lightgray,lightgray + shadow=black,gray + title=black,lightgray + button=black,cyan + actbutton=white,cyan + compactbutton=black,lightgray + checkbox=black,lightgray + actcheckbox=lightgray,cyan + entry=black,lightgray + disentry=gray,lightgray + label=black,lightgray + listbox=black,lightgray + actlistbox=black,cyan + sellistbox=lightgray,black + actsellistbox=lightgray,black + textbox=black,lightgray + acttextbox=black,cyan + emptyscale=,gray + fullscale=,cyan + helpline=white,black + roottext=lightgrey,black +' + +#======================================= +# SANITY CHECKS +#======================================= + +# Fail fast with a concise message when not using bash +# Single brackets are needed here for POSIX compatibility +# shellcheck disable=SC2292 +if [ -z "${BASH_VERSION:-}" ]; then + abort "Bash is required to interpret this script." +fi + +# Check if script is run with force-interactive mode in CI +if [[ -n "${CI-}" && -n "${INTERACTIVE-}" ]]; then + abort "Cannot run force-interactive mode in CI." +fi + +# Check if both `INTERACTIVE` and `NONINTERACTIVE` are set +# Always use single-quoted strings with `exp` expressions +# shellcheck disable=SC2016 +if [[ -n "${INTERACTIVE-}" && -n "${NONINTERACTIVE-}" ]]; then + abort 'Both `$INTERACTIVE` and `$NONINTERACTIVE` are set. Please unset at least one variable and try again.' +fi + +# Check if script is run in POSIX mode +if [[ -n "${POSIXLY_CORRECT+1}" ]]; then + abort 'Bash must not run in POSIX mode. Please unset POSIXLY_CORRECT and try again.' +fi diff --git a/src/screen_dialogs.sh b/src/screen_dialogs.sh new file mode 100644 index 0000000..fb6ac0c --- /dev/null +++ b/src/screen_dialogs.sh @@ -0,0 +1,101 @@ +#======================================= +# SCREEN DIALOGS +#======================================= + +echo_progress() { + local realProg=$1 # numerical real progress + local realMax=$2 # max value of that progress + local realStart=$3 # where real progress starts, % + local countr=$4 # real ticker, 3 ticks/s + local firstPhaseSecs=$5 # how long first, ticked part, last + + if [ "$realProg" -eq "0" ]; then + local progrsec=$(((countr * realStart) / (3 * firstPhaseSecs))) + if [ "$progrsec" -lt "$realStart" ]; then + echo "$progrsec" + else + echo "$realStart" + fi + else + echo $(((realProg * (100 - realStart) / realMax) + realStart)) + fi +} + +process_gauge() { + local process_to_measure=$1 + local lenmsg + lenmsg=$(echo "$4" | wc -l) + eval "$process_to_measure" & + local thepid=$! + local num=1 + while true; do + echo 0 + while kill -0 "$thepid" >/dev/null 2>&1; do + eval "$2" "$num" + num=$((num + 1)) + sleep 0.3 + done + echo 100 + break + done | whiptail --title "$3" --gauge "\n $4\n" $((lenmsg + 6)) 70 0 +} + +okdlg() { + local title=$1 + shift 1 + local msg="$*" + local lcount=$(echo -e "$msg" | grep -c '^') + local width=$(multiline_length "$msg") + whiptail --title "$title" --msgbox "$(center_multiline $((width + 4)) "$msg")" $((lcount + 6)) $((width + 9)) +} + +confirmdlg() { + local title=$1 + local btnlabel=$2 + shift 2 + local msg="$*" + local lcount=$(echo -e "$msg" | grep -c '^') + local width=$(multiline_length "$msg") + whiptail --title "$title" --ok-button "$btnlabel" --msgbox "$(center_multiline $((width + 4)) "$msg")" $((lcount + 6)) $((width + 9)) +} + +yesnodlg() { + yesnodlg_base "y" "$@" +} + +noyesdlg() { + yesnodlg_base "n" "$@" +} + +yesnodlg_base() { + local defaultbtn=$1 + local title=$2 + local ybtn=$3 + local nbtn=$4 + shift 4 + local msg="$*" + # shellcheck disable=SC2059 + local linec=$(printf "$msg" | grep -c '^') + local width=$(multiline_length "$msg") + local ylen=${#ybtn} + local nlen=${#nbtn} + # we need space for all < > around buttons + local minbtn=$((ylen + nlen + 6)) + # minimal nice width of dialog + local minlen=$((minbtn > 15 ? minbtn : 15)) + local mwidth=$((minlen > width ? minlen : width)) + + # whiptail has bug, buttons are NOT centered + local rpad=$((width < minbtn ? (nlen - 2) + ((nlen - 2) / 2) : 4)) + local padw=$((mwidth + rpad)) + + if [[ "$defaultbtn" == "y" ]]; then + whiptail --title "$title" --yesno "$(center_multiline "$padw" "$msg")" \ + --yes-button "$ybtn" --no-button "$nbtn" \ + $((linec + 7)) $((padw + 4)) + else + whiptail --title "$title" --yesno --defaultno "$(center_multiline "$padw" "$msg")" \ + --yes-button "$ybtn" --no-button "$nbtn" \ + $((linec + 7)) $((padw + 4)) + fi +} diff --git a/src/screen_formaters.sh b/src/screen_formaters.sh new file mode 100644 index 0000000..6e67277 --- /dev/null +++ b/src/screen_formaters.sh @@ -0,0 +1,54 @@ +#======================================= +# FORMATERS +#======================================= + +if [[ -t 1 ]]; then + tty_escape() { printf "\033[%sm" "$1"; } +else + tty_escape() { :; } +fi +tty_mkbold() { tty_escape "1;$1"; } +# tty_underline="$(tty_escape "4;39")" +tty_blue="$(tty_mkbold 34)" +tty_red="$(tty_mkbold 31)" +tty_bold="$(tty_mkbold 39)" +tty_reset="$(tty_escape 0)" + +NL="\n" +TL="\n\n" + +#======================================= +# EMOJIS +#======================================= + +emoji_check="\U2705" +emoji_ok="\U1F197" +emoji_err="\U274C" +emoji_note="\U1F4A1" + +uni_bullet=" $(printf '\u2022') " +uni_copyright="$(printf '\uA9\uFE0F')" +uni_bullet_pad=" " +uni_warn="$(printf "\U26A0")" + +uni_exit=" $(printf '\U274C') Wyjdź " +uni_start=" $(printf '\U1F984') Zaczynamy " +uni_menu=" $(printf '\U1F6E0') Menu " +uni_finish=" $(printf '\U1F984') Zamknij " +uni_reenter=" $(printf '\U21AA') Tak " +uni_noenter=" $(printf '\U2716') Nie " +uni_back=" $(printf '\U2B05') Wróć " +uni_select=" Wybierz " +uni_excl="$(printf '\U203C')" +uni_confirm_del=" $(printf '\U1F4A3') Tak " +uni_confirm_ch=" $(printf '\U1F199') Zmień " +uni_confirm_upd=" $(printf '\U1F199') Aktualizuj " +uni_confirm_ed=" $(printf '\U1F4DD') Edytuj " +uni_install=" $(printf '\U1F680') Instaluj " +uni_resign=" $(printf '\U1F6AB') Rezygnuję " +uni_send=" $(printf '\U1F4E7') Wyślij " +uni_delete=" $(printf '\U1F5D1') Usuń " +uni_leave_logs=" $(printf '\U1F4DC') Zostaw " + +uni_ns_ok="$(printf '\U1F7E2') działa" +uni_watchdog_ok="$(printf '\U1F415') Nightscout działa" diff --git a/src/utils.sh b/src/utils.sh new file mode 100644 index 0000000..92f6019 --- /dev/null +++ b/src/utils.sh @@ -0,0 +1,36 @@ +#======================================= +# UTILS +#======================================= + +# Search for the given executable in PATH (avoids a dependency on the `which` command) +which() { + # Alias to Bash built-in command `type -P` + type -P "$@" +} + +major_minor() { + echo "${1%%.*}.$( + x="${1#*.}" + echo "${x%%.*}" + )" +} + +version_gt() { + [[ "${1%.*}" -gt "${2%.*}" ]] || [[ "${1%.*}" -eq "${2%.*}" && "${1#*.}" -gt "${2#*.}" ]] +} +version_ge() { + [[ "${1%.*}" -gt "${2%.*}" ]] || [[ "${1%.*}" -eq "${2%.*}" && "${1#*.}" -ge "${2#*.}" ]] +} +version_lt() { + [[ "${1%.*}" -lt "${2%.*}" ]] || [[ "${1%.*}" -eq "${2%.*}" && "${1#*.}" -lt "${2#*.}" ]] +} + +if_is_set() { + [[ ${!1-x} == x ]] && return 1 || return 0 +} + +exit_on_no_cancel() { + if [ $? -eq 1 ]; then + exit 0 + fi +} diff --git a/src/utils_console.sh b/src/utils_console.sh new file mode 100644 index 0000000..2f13777 --- /dev/null +++ b/src/utils_console.sh @@ -0,0 +1,45 @@ +#======================================= +# CONSOLE OUTPUT UTILS +#======================================= + +shell_join() { + local arg + printf "%s" "$1" + shift + for arg in "$@"; do + printf " " + printf "%s" "${arg// /\ }" + done +} + +chomp() { + printf "%s" "${1/"$'\n'"/}" +} + +ohai() { + printf "${tty_blue}==>${tty_bold} %s${tty_reset}\n" "$(shell_join "$@")" +} + +msgok() { + # shellcheck disable=SC2059 + printf "$emoji_ok $1\n" +} + +msgnote() { + # shellcheck disable=SC2059 + printf "$emoji_note $1\n" +} + +msgcheck() { + # shellcheck disable=SC2059 + printf "$emoji_check $1\n" +} + +msgerr() { + # shellcheck disable=SC2059 + printf "$emoji_err $1\n" +} + +warn() { + printf "${tty_red}Warning${tty_reset}: %s\n" "$(chomp "$1")" >&2 +} diff --git a/src/utils_string.sh b/src/utils_string.sh new file mode 100644 index 0000000..3e8f68a --- /dev/null +++ b/src/utils_string.sh @@ -0,0 +1,95 @@ +#======================================= +# STRING UTILS +#======================================= + +join_by() { + local d=${1-} f=${2-} + if shift 2; then + printf %s "$f" "${@/#/$d}" + fi +} + +lpad_text() { + local inText="$1" + local len=${#inText} + local spaces=" " + if ((len == 0)); then + echo "" + else + echo "${spaces:0:$(($2 - len))}$1" + fi +} + +center_text() { + local inText="$1" + local len=${#inText} + local spaces=" " + if ((len == 0)); then + echo "" + else + echo "${spaces:0:$((($2 - len) / 2))}$1" + fi +} + +rpad_text() { + local inText="$1" + local len=${#inText} + local spaces=" " + if ((len == 0)); then + echo "" + else + local padSize=$(($2 - len)) + echo "$1${spaces:0:${padSize}}" + fi +} + +multiline_length() { + local string=$1 + local maxLen=0 + # shellcheck disable=SC2059 + readarray -t array <<<"$(printf "$string")" + for i in "${!array[@]}"; do + local line=${array[i]} + lineLen=${#line} + if [ "$lineLen" -gt "$maxLen" ]; then + maxLen="$lineLen" + fi + done + + echo "$maxLen" +} + +center_multiline() { + local maxLen=70 + local string="$*" + + if [ $# -gt 1 ]; then + maxLen=$1 + shift 1 + string="$*" + else + maxLen=$(multiline_length "$string") + fi + + # shellcheck disable=SC2059 + readarray -t array <<<"$(printf "$string")" + for i in "${!array[@]}"; do + local line=${array[i]} + # shellcheck disable=SC2005 + echo "$(center_text "$line" "$maxLen")" + done +} + +pad_multiline() { + + local string="$*" + local maxLen=$(multiline_length "$string") + + # shellcheck disable=SC2059 + readarray -t array <<<"$(printf "$string")" + for i in "${!array[@]}"; do + local line=${array[i]} + # shellcheck disable=SC2005 + echo "$(rpad_text "$line" "$maxLen")" + done +} diff --git a/tools/build.js b/tools/build.js index 04c6955..bf72521 100644 --- a/tools/build.js +++ b/tools/build.js @@ -16,14 +16,17 @@ const dateFormated = event.toISOString().substr(0, 10).replaceAll('-', '.'); try { let data = fs.readFileSync(srcDir + path.sep + 'setup.sh', 'utf8'); - data = data.replace(regexDevRemove, ''); - data = data.replace(regexInclude, (_, _2, fileName) => { - const included = fs.readFileSync(srcDir + path.sep + fileName, 'utf8'); - return included - }); - data = data.replace(regexVer, '$1'+manifest.version); - data = data.replace(regexScrVer, '$1'+manifest.version+'$3'); - data = data.replace(regexDate, '$1'+dateFormated+'$3'); + while (data.includes('#include') || data.includes('#dev-begin')) { + data = data.replace(regexDevRemove, ''); + data = data.replace(regexInclude, (_, _2, fileName) => { + const included = fs.readFileSync(srcDir + path.sep + fileName, 'utf8'); + return included+"\n\n" + }); + + data = data.replace(regexVer, '$1'+manifest.version); + data = data.replace(regexScrVer, '$1'+manifest.version+'$3'); + data = data.replace(regexDate, '$1'+dateFormated+'$3'); + } fs.writeFileSync(baseDir + path.sep + 'install.sh', data); } catch (err) { console.error(err); diff --git a/updated b/updated index 86fcf4e..d1c3dd2 100644 --- a/updated +++ b/updated @@ -1 +1 @@ -2026-01-04T22:33:05.041Z \ No newline at end of file +2026-01-05T15:40:04.465Z \ No newline at end of file