#!/bin/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 ### global variables SYSTEMD="/etc/systemd/system" INITD="/etc/init.d" ETCDEF="/etc/default" MOONRAKER_ENV="${HOME}/moonraker-env" MOONRAKER_DIR="${HOME}/moonraker" MOONRAKER_REPO="https://github.com/Arksine/moonraker.git" #===================================================# #================ INSTALL MOONRAKER ================# #===================================================# function system_check_moonraker(){ local major minor ### python 3 check status_msg "Your Python 3 version is: $(python3 --version)" major=$(python3 --version | cut -d" " -f2 | cut -d"." -f1) minor=$(python3 --version | cut -d"." -f2) if [ "${major}" -ge 3 ] && [ "${minor}" -ge 7 ]; then echo "true" else echo "false" fi } function moonraker_systemd() { local services services=$(find "${SYSTEMD}" -maxdepth 1 -regextype posix-extended -regex "${SYSTEMD}/moonraker(-[^0])?[0-9]*.service") echo "${services}" } function moonraker_setup_dialog(){ status_msg "Initializing Moonraker installation ..." ### return early if moonraker already exists if [ -n "$(moonraker_systemd)" ]; then ERROR_MSG="At least one Moonraker service is already installed:" for service in $(moonraker_systemd); do ERROR_MSG="${ERROR_MSG}\n ➔ ${service}" done export ERROR_MSG print_error && return fi ### return early if python version check fails if [ "$(system_check_moonraker)" == "false" ]; then ERROR_MSG="Versioncheck failed! Python 3.7 or newer required!\n" ERROR_MSG="${ERROR_MSG} Please upgrade Python." export ERROR_MSG print_error && return fi top_border if [ -f "${INITD}/klipper" ] || [ -f "${SYSTEMD}/klipper.service" ]; then printf "|${green}%-55s${default}|\n" " 1 Klipper instance was found!" elif [ "$(klipper_systemd | wc -w)" -gt 1 ]; then printf "|${green}%-55s${default}|\n" " $(klipper_systemd | wc -w) Klipper instances were found!" else echo -e "| ${yellow}INFO: No existing Klipper installation found!${default} |" fi echo -e "| Usually you need one Moonraker instance per Klipper |" echo -e "| instance. Though you can install as many as you wish. |" bottom_border local count while [[ ! (${count} =~ ^[1-9]+((0)+)?$) ]]; do read -p "${cyan}###### Number of Moonraker instances to set up:${default} " count if [[ ! (${count} =~ ^[1-9]+((0)+)?$) ]]; then error_msg "Invalid Input!\n" else echo read -p "${cyan}###### Install ${count} instance(s)? (Y/n):${default} " yn case "${yn}" in Y|y|Yes|yes|"") select_msg "Yes" status_msg "Installing ${count} Moonraker instance(s) ... \n" moonraker_setup "${count}" break;; N|n|No|no) select_msg"No" error_msg "Exiting Moonraker setup ...\n" break;; *) invalid_option && print_error ;; esac fi done } function install_moonraker_packages(){ local packages local install_script="${HOME}/moonraker/scripts/install-moonraker.sh" ### read PKGLIST from official install script status_msg "Reading dependencies..." # shellcheck disable=SC2016 packages="$(grep "PKGLIST=" "${install_script}" | cut -d'"' -f2 | sed 's/\${PKGLIST}//g' | tr -d '\n')" echo "${cyan}${packages}${white}" | tr '[:space:]' '\n' read -r -a packages <<< "${packages}" ### Update system package info status_msg "Updating lists of packages..." sudo apt-get update --allow-releaseinfo-change ### Install required packages status_msg "Installing packages..." sudo apt-get install --yes "${packages[@]}" } function create_moonraker_virtualenv(){ status_msg "Installing python virtual environment..." ### always create a clean virtualenv [ -d "${MOONRAKER_ENV}" ] && rm -rf "${MOONRAKER_ENV}" virtualenv -p /usr/bin/python3 "${MOONRAKER_ENV}" "${MOONRAKER_ENV}"/bin/pip install -r "${MOONRAKER_DIR}/scripts/moonraker-requirements.txt" } function moonraker_setup(){ local instances=${1} ### checking dependencies dep=(wget curl unzip dfu-util virtualenv) ### additional required dependencies on armbian dep+=(libjpeg-dev zlib1g-dev) dependency_check "${dep[@]}" ### step 1: clone moonraker status_msg "Downloading Moonraker ..." ### force remove existing moonraker dir and clone into fresh moonraker dir [ -d "${MOONRAKER_DIR}" ] && rm -rf "${MOONRAKER_DIR}" cd "${HOME}" && git clone "${MOONRAKER_REPO}" ### step 2: install moonraker dependencies and create python virtualenv status_msg "Installing dependencies ..." install_moonraker_packages create_moonraker_virtualenv ### step 3: create moonraker.conf create_moonraker_conf "${instances}" ### step 4: create moonraker instances create_moonraker_service "${instances}" ### step 5: create polkit rules for moonraker moonraker_polkit ### step 6: enable and start all instances do_action_service "enable" "moonraker" do_action_service "start" "moonraker" ### confirm message if [[ ${instances} -eq 1 ]]; then CONFIRM_MSG="Moonraker has been set up!" elif [[ ${instances} -gt 1 ]]; then CONFIRM_MSG="${instances} Moonraker instances have been set up!" fi export CONFIRM_MSG print_confirm print_mr_ip_list "${mr_ip_list}" return } function write_moonraker_service(){ local i=${1} cfg_dir=${2} cfg=${3} log=${4} service=${5} local service_template="${SRCDIR}/kiauh/resources/moonraker.service" ### replace all placeholders if [ ! -f "${service}" ]; then status_msg "Creating Moonraker Service ${i} ..." sudo cp "${service_template}" "${service}" [ -z "${i}" ] && sudo sed -i "s|instance %INST% ||" "${service}" [ -n "${i}" ] && sudo sed -i "s|%INST%|${i}|" "${service}" sudo sed -i "s|%USER%|${USER}|; s|%ENV%|${MOONRAKER_ENV}|; s|%DIR%|${MOONRAKER_DIR}|" "${service}" sudo sed -i "s|%CFG%|${cfg}|; s|%LOG%|${log}|" "${service}" fi } function create_moonraker_service(){ local instances=${1} if [ "${instances}" -eq 1 ]; then local i="" local cfg_dir="${KLIPPER_CONFIG}" local cfg="${cfg_dir}/moonraker.conf" local log="${HOME}/klipper_logs/moonraker.log" local service="${SYSTEMD}/moonraker.service" ### write single instance service write_moonraker_service "${i}" "${cfg_dir}" "${cfg}" "${log}" "${service}" ok_msg "Single Moonraker instance created!" elif [ "${instances}" -gt 1 ]; then local i=1 while [ "${i}" -le "${instances}" ]; do local cfg_dir="${KLIPPER_CONFIG}/printer_${i}" local cfg="${cfg_dir}/moonraker.conf" local log="${HOME}/klipper_logs/moonraker-${i}.log" local service="${SYSTEMD}/moonraker-${i}.service" ### write multi instance service write_moonraker_service "${i}" "${cfg_dir}" "${cfg}" "${log}" "${service}" ok_msg "Moonraker instance #${i} created!" i=$((i+1)) done && unset i ### enable mainsails remoteMode if mainsail is found if [ -d "${MAINSAIL_DIR}" ]; then status_msg "Mainsail installation found!" status_msg "Enabling Mainsail remoteMode ..." enable_mainsail_remotemode ok_msg "Mainsails remoteMode enabled!" fi else return 1 fi } function write_moonraker_conf(){ local cfg_dir=${1} cfg=${2} port=${3} log=${4} db=${5} uds=${6} lan=${7} local conf_template="${SRCDIR}/kiauh/resources/moonraker.conf" [ ! -d "${cfg_dir}" ] && mkdir -p "${cfg_dir}" if [ ! -f "${cfg}" ]; then status_msg "Creating moonraker.conf in ${cfg_dir} ..." cp "${conf_template}" "${cfg}" sed -i "s|%USER%|${USER}|g" "${cfg}" sed -i "s|%CFG%|${cfg_dir}|; s|%PORT%|${port}|; s|%LOG%|${log}|; s|%DB%|${db}|; s|%UDS%|${uds}|" "${cfg}" # if host ip is not in the default ip ranges replace placeholder, # otherwise remove placeholder from config if ! grep -q "${lan}" "${cfg}"; then sed -i "s|%LAN%|${lan}|" "${cfg}" else sed -i "/%LAN%/d" "${cfg}" fi ok_msg "moonraker.conf created!" else status_msg "File '${cfg_dir}/moonraker.conf' already exists!\nSkipping..." fi } function create_moonraker_conf(){ local instances=${1} local log="${HOME}/klipper_logs" local mr_ip_list=() local ip lan ip=$(hostname -I | cut -d" " -f1) lan="$(hostname -I | cut -d" " -f1 | cut -d"." -f1-2).0.0/16" if [ "${instances}" -eq 1 ]; then local cfg_dir="${KLIPPER_CONFIG}" local cfg="${cfg_dir}/moonraker.conf" local port=7125 local db="${HOME}/.moonraker_database" local uds="/tmp/klippy_uds" ### write single instance config write_moonraker_conf "${cfg_dir}" "${cfg}" "${port}" "${log}" "${db}" "${uds}" "${lan}" mr_ip_list+=("${ip}:${port}") elif [ "${instances}" -gt 1 ]; then local i=1 while [ "${i}" -le "${instances}" ]; do local cfg_dir="${KLIPPER_CONFIG}/printer_${i}" local cfg="${cfg_dir}/moonraker.conf" local port=7125 local db="${HOME}/.moonraker_database_${i}" local uds="/tmp/klippy_uds-${i}" ### write multi instance config write_moonraker_conf "${cfg_dir}" "${cfg}" "${port}" "${log}" "${db}" "${uds}" "${lan}" mr_ip_list+=("${ip}:${port}") port=$((port+1)) i=$((i+1)) done && unset port i else return 1 fi export mr_ip_list } function print_mr_ip_list(){ local ips=${1} local i=0 for ip in "${ips[@]}"; do echo -e " ${cyan}● Instance $((i + 1)):${white} ${ip}" done } ### introduced due to ### https://github.com/Arksine/moonraker/issues/349 ### https://github.com/Arksine/moonraker/pull/346 function moonraker_polkit(){ local has_sup ### check for required SupplementaryGroups entry in service files ### write it to the service if it doesn't exist for service in $(moonraker_systemd); do has_sup="$(grep "SupplementaryGroups=moonraker-admin" "${service}")" if [ -z "${has_sup}" ]; then status_msg "Adding moonraker-admin supplementary group to ${service} ..." sudo sed -i "/^Type=simple$/a SupplementaryGroups=moonraker-admin" "${service}" ok_msg "Adding moonraker-admin supplementary group successfull!" fi done [ -z "${has_sup}" ] && echo "reloading services!!!" && sudo systemctl daemon-reload ### execute moonrakers policykit-rules script /bin/bash "${HOME}/moonraker/scripts/set-policykit-rules.sh" } #==================================================# #================ REMOVE MOONRAKER ================# #==================================================# function remove_moonraker_sysvinit() { [ ! -e "${INITD}/moonraker" ] && return status_msg "Removing Moonraker SysVinit service ..." sudo systemctl stop moonraker sudo update-rc.d -f moonraker remove sudo rm -f "${INITD}/moonraker" "${ETCDEF}/moonraker" ok_msg "Moonraker SysVinit service removed!" } function remove_moonraker_systemd() { [ -z "$(moonraker_systemd)" ] && return status_msg "Removing Moonraker Systemd Services ..." local files for service in $(moonraker_systemd | cut -d"/" -f5) do status_msg "Removing ${service} ..." sudo systemctl stop "${service}" sudo systemctl disable "${service}" sudo rm -f "${SYSTEMD}/${service}" ok_msg "Done!" done ### reloading units sudo systemctl daemon-reload sudo systemctl reset-failed ok_msg "Moonraker Services removed!" } function remove_moonraker_logs() { local files files=$(find "${HOME}/klipper_logs" -maxdepth 1 -regextype posix-extended -regex "${HOME}/klipper_logs/moonraker(-[^0])?[0-9]*\.log(.*)?") if [ -n "${files}" ]; then for file in ${files}; do status_msg "Removing ${file} ..." rm -f "${file}" ok_msg "${file} removed!" done fi } function remove_moonraker_api_key() { ### remove legacy api key if [ -e "${HOME}/.klippy_api_key" ]; then status_msg "Removing legacy API Key ..." rm "${HOME}/.klippy_api_key" ok_msg "Done!" fi ### remove api key if [ -e "${HOME}/.moonraker_api_key" ]; then status_msg "Removing API Key ..." rm "${HOME}/.moonraker_api_key" ok_msg "Done!" fi } function remove_moonraker_dir() { [ ! -d "${MOONRAKER_DIR}" ] && return status_msg "Removing Moonraker directory ..." rm -rf "${MOONRAKER_DIR}" ok_msg "Directory removed!" } function remove_moonraker_env() { [ ! -d "${MOONRAKER_ENV}" ] && return status_msg "Removing moonraker-env directory ..." rm -rf "${MOONRAKER_ENV}" ok_msg "Directory removed!" } function remove_moonraker_polkit() { status_msg "Removing all Moonraker PolicyKit rules ..." /bin/bash "${HOME}/moonraker/scripts/set-policykit-rules.sh" --clear ok_msg "Done!" } #TODO this is technically not moonraker but rather webinterface related configs, so this should be refactored. function remove_moonraker_nginx() { if [[ -e "${NGINX_CONFD}/upstreams.conf" || -e "${NGINX_CONFD}/common_vars.conf" ]]; then status_msg "Removing Moonraker NGINX configuration ..." sudo rm -f "${NGINX_CONFD}/upstreams.conf" "${NGINX_CONFD}/common_vars.conf" ok_msg "Moonraker NGINX configuration removed!" fi } function remove_moonraker(){ remove_moonraker_sysvinit remove_moonraker_systemd remove_moonraker_logs remove_moonraker_api_key remove_moonraker_dir remove_moonraker_env remove_moonraker_polkit remove_moonraker_nginx confirm="Moonraker was successfully removed!" print_confirm "${confirm}" && return } #==================================================# #================ UPDATE MOONRAKER ================# #==================================================# function update_moonraker(){ do_action_service "stop" "moonraker" if [ ! -d "${MOONRAKER_DIR}" ]; then cd "${HOME}" && git clone "${MOONRAKER_REPO}" else bb4u "moonraker" status_msg "Updating Moonraker ..." cd "${MOONRAKER_DIR}" && git pull ### read PKGLIST and install possible new dependencies install_moonraker_packages ### install possible new python dependencies "${MOONRAKER_ENV}"/bin/pip install -r "${MOONRAKER_DIR}/scripts/moonraker-requirements.txt" fi ### required due to https://github.com/Arksine/moonraker/issues/349 moonraker_polkit update_log_paths "moonraker" ok_msg "Update complete!" do_action_service "restart" "moonraker" } #==================================================# #================ MOONRAKER STATUS ================# #==================================================# function get_moonraker_status(){ local sf_count status sf_count="$(moonraker_systemd | wc -w)" ### remove the "SERVICE" entry from the data array if a moonraker service is installed local data_arr=(SERVICE "${MOONRAKER_DIR}" "${MOONRAKER_ENV}") [ "${sf_count}" -gt 0 ] && unset "data_arr[0]" ### count+1 for each found data-item from array local filecount=0 for data in "${data_arr[@]}"; do [ -e "${data}" ] && filecount=$(("${filecount}" + 1)) done if [ "${filecount}" == "${#data_arr[*]}" ]; then status="$(printf "${green}Installed: %-5s${white}" "${sf_count}")" elif [ "${filecount}" == 0 ]; then status="${red}Not installed!${white} " else status="${yellow}Incomplete!${white} " fi echo "${status}" } function get_local_moonraker_commit(){ if [ -d "${MOONRAKER_DIR}" ] && [ -d "${MOONRAKER_DIR}"/.git ]; then cd "${MOONRAKER_DIR}" commit="$(git describe HEAD --always --tags | cut -d "-" -f 1,2)" else commit="${NONE}" fi echo "${commit}" } function get_remote_moonraker_commit(){ if [ -d "${MOONRAKER_DIR}" ] && [ -d "${MOONRAKER_DIR}"/.git ]; then cd "${MOONRAKER_DIR}" git fetch origin -q commit=$(git describe origin/master --always --tags | cut -d "-" -f 1,2) else commit="${NONE}" fi echo "${commit}" } function compare_moonraker_versions(){ unset MOONRAKER_UPDATE_AVAIL if [ "$(get_local_moonraker_commit)" != "$(get_remote_moonraker_commit)" ]; then LOCAL_COMMIT="${yellow}$(printf "%-12s" "$(get_local_moonraker_commit)")${white}" REMOTE_COMMIT="${green}$(printf "%-12s" "$(get_remotemoonrakerr_commit)")${white}" # add klipper to the update all array for the update all function in the updater MOONRAKER_UPDATE_AVAIL="true" && update_arr+=(update_moonraker) else LOCAL_COMMIT="${green}$(printf "%-12s" "$(get_remote_moonraker_commit)")${white}" REMOTE_COMMIT="${green}$(printf "%-12s" "$(get_remote_moonraker_commit)")${white}" KLIPPER_UPDATE_AVAIL="false" fi }