#!/usr/bin/env bash #=======================================================================# # Copyright (C) 2020 - 2022 Dominik Willner # # # # This file is part of KIAUH - Klipper Installation And Update Helper # # https://github.com/th33xitus/kiauh # # # # This file may be distributed under the terms of the GNU GPLv3 license # #=======================================================================# set -e #================================================# #=================== STARTUP ====================# #================================================# function check_euid() { if [[ ${EUID} -eq 0 ]]; then echo -e "${red}" top_border echo -e "| !!! THIS SCRIPT MUST NOT RUN AS ROOT !!! |" bottom_border echo -e "${white}" exit 1 fi } #================================================# #============= MESSAGE FORMATTING ===============# #================================================# function select_msg() { echo -e "${white} [➔] ${1}" } function status_msg() { echo -e "\n${magenta}###### ${1}${white}" } function ok_msg() { echo -e "${green}[✓ OK] ${1}${white}" } function warn_msg() { echo -e "${yellow}>>>>>> ${1}${white}" } function error_msg() { echo -e "${red}>>>>>> ${1}${white}" } function abort_msg() { echo -e "${red}<<<<<< ${1}${white}" } function title_msg() { echo -e "${cyan}${1}${white}" } function print_error() { [[ -z ${1} ]] && return echo -e "${red}" echo -e "#=======================================================#" echo -e " ${1} " echo -e "#=======================================================#" echo -e "${white}" } function print_confirm() { [[ -z ${1} ]] && return echo -e "${green}" echo -e "#=======================================================#" echo -e " ${1} " echo -e "#=======================================================#" echo -e "${white}" } #================================================# #=================== LOGGING ====================# #================================================# function timestamp() { date +"[%F %T]" } function init_logfile() { local log="/tmp/kiauh.log" { echo -e "#================================================================#" echo -e "# New KIAUH session started on: $(date) #" echo -e "#================================================================#" echo -e "KIAUH $(get_kiauh_version)" echo -e "#================================================================#" } >> "${log}" } function log_info() { local message="${1}" log="${LOGFILE}" echo -e "$(timestamp) [INFO]: ${message}" | tr -s " " >> "${log}" } function log_warning() { local message="${1}" log="${LOGFILE}" echo -e "$(timestamp) [WARN]: ${message}" | tr -s " " >> "${log}" } function log_error() { local message="${1}" log="${LOGFILE}" echo -e "$(timestamp) [ERR]: ${message}" | tr -s " " >> "${log}" } #================================================# #=============== KIAUH SETTINGS =================# #================================================# function read_kiauh_ini() { local func=${1} if [[ ! -f ${INI_FILE} ]]; then log_warning "Reading from .kiauh.ini failed! File not found! Creating default ini file." init_ini fi log_info "Reading from .kiauh.ini ... (${func})" source "${INI_FILE}" } function init_ini() { ### remove pre-version 4 ini files if [[ -f ${INI_FILE} ]] && ! grep -Eq "^# KIAUH v4\.0\.0$" "${INI_FILE}"; then rm "${INI_FILE}" fi ### initialize v4.0.0 ini file if [[ ! -f ${INI_FILE} ]]; then { echo -e "# File creation date: $(date)" echo -e "#=================================================#" echo -e "# KIAUH - Klipper Installation And Update Helper #" echo -e "# https://github.com/th33xitus/kiauh #" echo -e "# DO NOT edit this file! #" echo -e "#=================================================#" echo -e "# KIAUH v4.0.0" } >> "${INI_FILE}" fi if ! grep -Eq "^backup_before_update=." "${INI_FILE}"; then echo -e "\nbackup_before_update=false\c" >> "${INI_FILE}" fi if ! grep -Eq "^logupload_accepted=." "${INI_FILE}"; then echo -e "\nlogupload_accepted=false\c" >> "${INI_FILE}" fi if ! grep -Eq "^custom_klipper_cfg_loc=" "${INI_FILE}"; then echo -e "\ncustom_klipper_cfg_loc=\c" >> "${INI_FILE}" fi if ! grep -Eq "^custom_klipper_repo=" "${INI_FILE}"; then echo -e "\ncustom_klipper_repo=\c" >> "${INI_FILE}" fi if ! grep -Eq "^custom_klipper_repo_branch=" "${INI_FILE}"; then echo -e "\ncustom_klipper_repo_branch=\c" >> "${INI_FILE}" fi if ! grep -Eq "^mainsail_install_unstable=" "${INI_FILE}"; then echo -e "\nmainsail_install_unstable=false\c" >> "${INI_FILE}" fi if ! grep -Eq "^fluidd_install_unstable=" "${INI_FILE}"; then echo -e "\nfluidd_install_unstable=false\c" >> "${INI_FILE}" fi fetch_webui_ports } function change_klipper_cfg_folder() { local current_cfg_loc example_loc recommended_loc new_cfg_loc current_cfg_loc="$(get_klipper_cfg_dir)" example_loc=$(printf "%s/" "${HOME}") recommended_loc=$(printf "%s/klipper_config" "${HOME}") local yn while true; do top_border echo -e "| ${yellow}IMPORTANT:${white} |" echo -e "| Please enter the new path in the following format: |" printf "| ${cyan}%-51s${white} |\n" "${example_loc}" blank_line echo -e "| ${red}WARNING: ${white} |" echo -e "| ${red}There will be no validation checks! Make sure to set${white} |" echo -e "| ${red}a valid directory to prevent possible problems!${white} |" blank_line printf "| Recommended: ${cyan}%-38s${white} |\n" "${recommended_loc}" bottom_border echo echo -e "${cyan}###### Please set the new Klipper config directory:${white} " read -e -i "${current_cfg_loc}" -e new_cfg_loc echo read -p "${cyan}###### Set config directory to '${yellow}${new_cfg_loc}${cyan}' ? (Y/n):${white} " yn case "${yn}" in Y|y|Yes|yes|"") select_msg "Yes" set_klipper_cfg_path "${current_cfg_loc}" "${new_cfg_loc}" print_confirm "New config directory set!" break;; N|n|No|no) select_msg "No" break;; *) print_error "Invalid command!";; esac done } function set_klipper_cfg_path() { local current_cfg_loc="${1}" new_cfg_loc="${2}" local instance klipper_services moonraker_services moonraker_configs log_info "Function set_klipper_cfg_path invoked\nCurrent location: ${1}\nNew location: ${2}" ### backup the old config dir backup_klipper_config_dir ### write new location to .kiauh.ini sed -i "/^custom_klipper_cfg_loc=/d" "${INI_FILE}" sed -i '$a'"custom_klipper_cfg_loc=${new_cfg_loc}" "${INI_FILE}" status_msg "New directory was set to '${new_cfg_loc}'!" ### stop services do_action_service "stop" "klipper" do_action_service "stop" "moonraker" ### copy config files to new klipper config folder if [[ -n ${current_cfg_loc} && -d ${current_cfg_loc} ]]; then status_msg "Copy config files to '${new_cfg_loc}' ..." if [[ ! -d ${new_cfg_loc} ]]; then log_info "Copy process started" mkdir -p "${new_cfg_loc}" cd "${current_cfg_loc}" cp -r -v ./* "${new_cfg_loc}" ok_msg "Done!" else log_warning "Copy process skipped, new config directory already exists and may not be empty!" warn_msg "New config directory already exists! Copy process skipped!" fi fi klipper_services=$(klipper_systemd) if [[ -n ${klipper_services} ]]; then status_msg "Re-writing Klipper services to use new config file location ..." for service in ${klipper_services}; do if [[ ${service} = "/etc/systemd/system/klipper.service" ]]; then if grep -q "Environment=KLIPPER_CONFIG=" "${service}"; then ### single instance klipper service installed by kiauh v4 / MainsailOS > 0.5.0 sudo sed -i -r "/KLIPPER_CONFIG=/ s|CONFIG=(.+)\/printer\.cfg|CONFIG=${new_cfg_loc}/printer\.cfg|" "${service}" else ### single instance klipper service installed by kiauh v3 sudo sed -i -r "/ExecStart=/ s|klippy\.py (.+)\/printer\.cfg|klippy\.py ${new_cfg_loc}\/printer\.cfg|" "${service}" fi else instance=$(echo "${service}" | cut -d"-" -f2 | cut -d"." -f1) if grep -q "Environment=KLIPPER_CONFIG=" "${service}"; then ### multi instance klipper service installed by kiauh v4 / MainsailOS > 0.5.0 sudo sed -i -r "/KLIPPER_CONFIG=/ s|CONFIG=(.+)\/printer_${instance}\/printer\.cfg|CONFIG=${new_cfg_loc}\/printer_${instance}\/printer\.cfg|" "${service}" else ### multi instance klipper service installed by kiauh v3 sudo sed -i -r "/ExecStart=/ s|klippy\.py (.+)\/printer_${instance}\/printer\.cfg|klippy\.py ${new_cfg_loc}\/printer_${instance}\/printer\.cfg|" "${service}" fi fi done ok_msg "OK!" fi moonraker_services=$(moonraker_systemd) if [[ -n ${moonraker_services} ]]; then ### handle multi moonraker instance service file status_msg "Re-writing Moonraker services to use new config file location ..." for service in ${moonraker_services}; do if [[ ${service} = "/etc/systemd/system/moonraker.service" ]]; then if grep -q "Environment=MOONRAKER_CONF=" "${service}"; then ### single instance moonraker service installed by kiauh v4 / MainsailOS > 0.5.0 sudo sed -i -r "/MOONRAKER_CONF=/ s|_CONF=(.+)\/moonraker\.conf|_CONF=${new_cfg_loc}\/moonraker\.conf|" "${service}" else ### single instance moonraker service installed by kiauh v3 sudo sed -i -r "/ExecStart=/ s| -c (.+)\/moonraker\.conf| -c ${new_cfg_loc}\/moonraker\.conf|" "${service}" fi else instance=$(echo "${service}" | cut -d"-" -f2 | cut -d"." -f1) if grep -q "Environment=MOONRAKER_CONF=" "${service}"; then ### multi instance moonraker service installed by kiauh v4 / MainsailOS > 0.5.0 sudo sed -i -r "/MOONRAKER_CONF=/ s|_CONF=(.+)\/printer_${instance}\/moonraker\.conf|_CONF=${new_cfg_loc}\/printer_${instance}\/moonraker\.conf|" "${service}" else ### multi instance moonraker service installed by kiauh v3 sudo sed -i -r "/ExecStart=/ s| -c (.+)\/printer_${instance}\/moonraker\.conf| -c ${new_cfg_loc}\/printer_${instance}\/moonraker\.conf|" "${service}" fi fi done moonraker_configs=$(find "${new_cfg_loc}" -type f -name "moonraker.conf" | sort) ### replace old file path with new one in moonraker.conf local loc for conf in ${moonraker_configs}; do loc=$(echo "${conf}" | rev | cut -d"/" -f2- | rev) sed -i -r "/config_path:/ s|config_path:.*|config_path: ${loc}|" "${conf}" done ok_msg "OK!" fi ### reloading units sudo systemctl daemon-reload ### restart services do_action_service "restart" "klipper" do_action_service "restart" "moonraker" } function switch_mainsail_releasetype() { read_kiauh_ini "${FUNCNAME[0]}" local state="${mainsail_install_unstable}" if [[ ${state} == "false" ]]; then sed -i '/mainsail_install_unstable=/s/false/true/' "${INI_FILE}" log_info "mainsail_install_unstable changed (false -> true) " else sed -i '/mainsail_install_unstable=/s/true/false/' "${INI_FILE}" log_info "mainsail_install_unstable changed (true -> false) " fi } function switch_fluidd_releasetype() { read_kiauh_ini "${FUNCNAME[0]}" local state="${fluidd_install_unstable}" if [[ ${state} == "false" ]]; then sed -i '/fluidd_install_unstable=/s/false/true/' "${INI_FILE}" log_info "fluidd_install_unstable changed (false -> true) " else sed -i '/fluidd_install_unstable=/s/true/false/' "${INI_FILE}" log_info "fluidd_install_unstable changed (true -> false) " fi } function toggle_backup_before_update() { read_kiauh_ini "${FUNCNAME[0]}" local state="${backup_before_update}" if [[ ${state} = "false" ]]; then sed -i '/backup_before_update=/s/false/true/' "${INI_FILE}" else sed -i '/backup_before_update=/s/true/false/' "${INI_FILE}" fi } function set_custom_klipper_repo() { read_kiauh_ini "${FUNCNAME[0]}" local repo=${1} branch=${2} sed -i "/^custom_klipper_repo=/d" "${INI_FILE}" sed -i '$a'"custom_klipper_repo=${repo}" "${INI_FILE}" sed -i "/^custom_klipper_repo_branch=/d" "${INI_FILE}" sed -i '$a'"custom_klipper_repo_branch=${branch}" "${INI_FILE}" } #================================================# #=============== HANDLE SERVICES ================# #================================================# function do_action_service() { local services action=${1} service=${2} services=$(find "${SYSTEMD}" -maxdepth 1 -regextype posix-extended -regex "${SYSTEMD}/${service}(-[0-9a-zA-Z]+)?.service" | sort) if [[ -n ${services} ]]; then for service in ${services}; do service=$(echo "${service}" | rev | cut -d"/" -f1 | rev) status_msg "${action^} ${service} ..." if sudo systemctl "${action}" "${service}"; then log_info "${service}: ${action} > success" ok_msg "${action^} ${service} successfull!" else log_warning "${service}: ${action} > failed" warn_msg "${action^} ${service} failed!" fi done fi } #================================================# #================ DEPENDENCIES ==================# #================================================# ### returns 'true' if python version >= 3.7 function python3_check() { local major minor passed major=$(python3 --version | cut -d" " -f2 | cut -d"." -f1) minor=$(python3 --version | cut -d"." -f2) if (( major >= 3 && minor >= 7 )); then passed="true" else passed="false" fi echo "${passed}" } function dependency_check() { local dep=( "${@}" ) local packages status_msg "Checking for the following dependencies:" #check if package is installed, if not write its name into array for pkg in "${dep[@]}"; do echo -e "${cyan}● ${pkg} ${white}" [[ ! $(dpkg-query -f'${Status}' --show "${pkg}" 2>/dev/null) = *\ installed ]] && \ packages+=("${pkg}") done #if array is not empty, install packages from array if (( ${#packages[@]} > 0 )); then status_msg "Installing the following dependencies:" for package in "${packages[@]}"; do echo -e "${cyan}● ${package} ${white}" done echo if sudo apt-get update --allow-releaseinfo-change && sudo apt-get install "${packages[@]}" -y; then ok_msg "Dependencies installed!" else error_msg "Installing dependencies failed!" return 1 # exit kiauh fi else ok_msg "Dependencies already met!" return fi } function fetch_webui_ports() { local port interfaces=("mainsail" "fluidd" "octoprint") ### read ports from possible installed interfaces and write them to ~/.kiauh.ini for interface in "${interfaces[@]}"; do if [[ -f "/etc/nginx/sites-available/${interface}" ]]; then port=$(grep -E "listen" "/etc/nginx/sites-available/${interface}" | head -1 | sed 's/^\s*//' | sed 's/;$//' | cut -d" " -f2) if ! grep -Eq "${interface}_port" "${INI_FILE}"; then sed -i '$a'"${interface}_port=${port}" "${INI_FILE}" else sed -i "/^${interface}_port/d" "${INI_FILE}" sed -i '$a'"${interface}_port=${port}" "${INI_FILE}" fi else sed -i "/^${interface}_port/d" "${INI_FILE}" fi done } #================================================# #=================== SYSTEM =====================# #================================================# function check_system_updates() { local updates_avail info_msg updates_avail=$(apt list --upgradeable 2>/dev/null | sed "1d") if [[ -n ${updates_avail} ]]; then # add system updates to the update all array for the update all function in the updater SYS_UPDATE_AVAIL="true" && update_arr+=(update_system) info_msg="${yellow}System upgrade available!${white}" else SYS_UPDATE_AVAIL="false" info_msg="${green}System up to date! ${white}" fi echo "${info_msg}" } function update_system() { status_msg "Updating System ..." if sudo apt-get update --allow-releaseinfo-change && sudo apt-get upgrade -y; then print_confirm "Update complete! Check the log above!\n ${yellow}KIAUH will not install any dist-upgrades or\n any packages which have been kept back!${green}" else print_error "System update failed! Please watch for any errors printed above!" fi } function check_usergroups() { local group_dialout group_tty if grep -q "dialout" .local' in the browser. |" echo -e "| |" echo -e "| E.g.: If you set the hostname to 'my-printer' you |" echo -e "| can open Mainsail / Fluidd / Octoprint by |" echo -e "| browsing to: http://my-printer.local |" bottom_border local yn while true; do read -p "${cyan}###### Do you want to change the hostname? (y/N):${white} " yn case "${yn}" in Y|y|Yes|yes) select_msg "Yes" change_hostname break;; N|n|No|no|"") select_msg "No" break;; *) error_msg "Invalid command!";; esac done } function change_hostname() { local new_hostname regex="^[^\-\_]+([0-9a-z]\-{0,1})+[^\-\_]+$" echo top_border echo -e "| ${green}Allowed characters: a-z, 0-9 and single '-'${white} |" echo -e "| ${red}No special characters allowed!${white} |" echo -e "| ${red}No leading or trailing '-' allowed!${white} |" bottom_border while true; do read -p "${cyan}###### Please set the new hostname:${white} " new_hostname if [[ ${new_hostname} =~ ${regex} ]]; then local yn while true; do echo read -p "${cyan}###### Do you want '${new_hostname}' to be the new hostname? (Y/n):${white} " yn case "${yn}" in Y|y|Yes|yes|"") select_msg "Yes" set_hostname "${new_hostname}" break;; N|n|No|no) select_msg "No" abort_msg "Skip hostname change ..." break;; *) print_error "Invalid command!";; esac done else warn_msg "'${new_hostname}' is not a valid hostname!" fi break done } function set_hostname() { local new_hostname=${1} current_date #check for dependencies local dep=(avahi-daemon) dependency_check "${dep[@]}" #create host file if missing or create backup of existing one with current date&time if [[ -f /etc/hosts ]]; then current_date=$(get_date) status_msg "Creating backup of hosts file ..." sudo cp "/etc/hosts" "/etc/hosts.${current_date}.bak" ok_msg "Backup done!" ok_msg "File:'/etc/hosts.${current_date}.bak'" else sudo touch /etc/hosts fi #set new hostname in /etc/hostname status_msg "Setting hostname to '${new_hostname}' ..." status_msg "Please wait ..." sudo hostnamectl set-hostname "${new_hostname}" #write new hostname to /etc/hosts status_msg "Writing new hostname to /etc/hosts ..." echo "127.0.0.1 ${new_hostname}" | sudo tee -a /etc/hosts &>/dev/null ok_msg "New hostname successfully configured!" ok_msg "Remember to reboot for the changes to take effect!" } ### this function takes in the full path of a systemd service file and returns ### either the instance index or the custom name ### input: /etc/systemd/system/klipper-name.service ### returns: name function get_instance_name() { local instance=${1} name name=$(echo "${instance}" | rev | cut -d"/" -f1 | rev | cut -d"-" -f2 | cut -d"." -f1) echo "${name}" }