From 1c41abe40f0a6acc23af212840ee0d626605af31 Mon Sep 17 00:00:00 2001 From: th33xitus Date: Mon, 5 Oct 2020 14:14:26 +0200 Subject: [PATCH 01/12] bugfix: fix the wrong displaying of color codes in windows-consoles --- kiauh.sh | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/kiauh.sh b/kiauh.sh index ccc9bc9..946311d 100755 --- a/kiauh.sh +++ b/kiauh.sh @@ -4,11 +4,11 @@ set -e ### set some variables ERROR_MSG="" -green=$(echo -en "\001\033[01;32m\002") -yellow=$(echo -en "\001\033[01;33m\002") -red=$(echo -en "\001\033[01;31m\002") -cyan=$(echo -en "\001\033[01;36m\002") -default=$(echo -en "\001\033[0m\002") +green=$(echo -en "\e[92m") +yellow=$(echo -en "\e[93m") +red=$(echo -en "\e[91m") +cyan=$(echo -en "\e[96m") +default=$(echo -en "\e[97m") ### set important directories #klipper From 9a4ca4114c370397e8d2a9db229ebddd4934aa83 Mon Sep 17 00:00:00 2001 From: th33xitus Date: Mon, 5 Oct 2020 14:30:52 +0200 Subject: [PATCH 02/12] fix: more verbose kiauh update message --- scripts/ui/general_ui.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/scripts/ui/general_ui.sh b/scripts/ui/general_ui.sh index 1867a44..70e26cf 100755 --- a/scripts/ui/general_ui.sh +++ b/scripts/ui/general_ui.sh @@ -29,5 +29,8 @@ kiauh_update_msg(){ top_border echo -e "| ${yellow}There is a newer version of this script available!${default} | " echo -e "| ${yellow}Type 'update' if you want to update KIAUH now.${default} | " + echo -e "| | " + echo -e "| ${yellow}Check out the KIAUH changelog for important changes${default} | " + echo -e "| ${yellow}either to the script or the installable components!${default} | " bottom_border } From fe941f4227aae2f82f3b7e7ba022e7f8270d9403 Mon Sep 17 00:00:00 2001 From: th33xitus Date: Tue, 6 Oct 2020 15:03:54 +0200 Subject: [PATCH 03/12] feature: initial commit to add installer for Fluidd + rewrite nginx related stuff --- kiauh.sh | 19 ++-- resources/common_vars_nginx.cfg | 6 ++ resources/fluidd_nginx.cfg | 77 +++++++++++++++ resources/mainsail_nginx.cfg | 27 +++--- resources/moonraker_nginx.cfg | 13 +++ scripts/install_dwc2.sh | 56 +++++++++-- scripts/install_fluidd.sh | 75 +++++++++++++++ scripts/install_mainsail.sh | 70 +++++++++----- scripts/install_moonraker.sh | 96 +++++++++++++------ scripts/install_octoprint.sh | 53 ++++++++-- ..._reverse_proxy.sh => network_functions.sh} | 82 +++++++++++++--- scripts/remove.sh | 34 +++++++ scripts/status.sh | 85 ++++++++++++---- scripts/ui/general_ui.sh | 6 +- scripts/ui/install_menu.sh | 10 +- scripts/ui/main_menu.sh | 1 + scripts/ui/remove_menu.sh | 15 ++- scripts/ui/update_menu.sh | 1 + 18 files changed, 592 insertions(+), 134 deletions(-) create mode 100644 resources/common_vars_nginx.cfg create mode 100644 resources/fluidd_nginx.cfg create mode 100644 resources/moonraker_nginx.cfg create mode 100755 scripts/install_fluidd.sh rename scripts/{set_reverse_proxy.sh => network_functions.sh} (58%) diff --git a/kiauh.sh b/kiauh.sh index 946311d..2b04a44 100755 --- a/kiauh.sh +++ b/kiauh.sh @@ -16,16 +16,23 @@ KLIPPER_DIR=${HOME}/klipper KLIPPY_ENV_DIR=${HOME}/klippy-env KLIPPER_SERVICE1=/etc/init.d/klipper KLIPPER_SERVICE2=/etc/default/klipper -#dwc2 -DWC2FK_DIR=${HOME}/dwc2-for-klipper-socket -DWC_ENV_DIR=${HOME}/dwc-env -DWC2_DIR=${HOME}/sdcard/web -#mainsail/moonraker -MAINSAIL_DIR=${HOME}/mainsail +#nginx +NGINX_SA=/etc/nginx/sites_available +NGINX_SE=/etc/nginx/sites_enabled +NGINX_CONFD=/etc/nginx/conf.d +#moonraker MOONRAKER_DIR=${HOME}/moonraker MOONRAKER_ENV_DIR=${HOME}/moonraker-env MOONRAKER_SERVICE1=/etc/init.d/moonraker MOONRAKER_SERVICE2=/etc/default/moonraker +#mainsail +MAINSAIL_DIR=${HOME}/mainsail +#fluidd +FLUIDD_DIR=${HOME}/fluidd +#dwc2 +DWC2FK_DIR=${HOME}/dwc2-for-klipper-socket +DWC_ENV_DIR=${HOME}/dwc-env +DWC2_DIR=${HOME}/sdcard/web #octoprint OCTOPRINT_DIR=${HOME}/OctoPrint OCTOPRINT_CFG_DIR=${HOME}/.octoprint diff --git a/resources/common_vars_nginx.cfg b/resources/common_vars_nginx.cfg new file mode 100644 index 0000000..9c3f85e --- /dev/null +++ b/resources/common_vars_nginx.cfg @@ -0,0 +1,6 @@ +# /etc/nginx/conf.d/common_vars.conf + +map $http_upgrade $connection_upgrade { + default upgrade; + '' close; +} \ No newline at end of file diff --git a/resources/fluidd_nginx.cfg b/resources/fluidd_nginx.cfg new file mode 100644 index 0000000..ee0cfa2 --- /dev/null +++ b/resources/fluidd_nginx.cfg @@ -0,0 +1,77 @@ +# /etc/nginx/sites-available/fluidd + +server { + listen 80; + listen [::]:80; + + access_log /var/log/nginx/fluidd-access.log; + error_log /var/log/nginx/fluidd-error.log; + + #web_path from fluidd static files + root /home/pi/fluidd; + + index index.html; + server_name _; + + #max upload size for gcodes + client_max_body_size 200M; + + location / { + try_files $uri $uri/ /index.html; + } + + location /printer { + proxy_pass http://apiserver/printer; + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Scheme $scheme; + } + + location /api { + proxy_pass http://apiserver/api; + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Scheme $scheme; + } + + location /access { + proxy_pass http://apiserver/access; + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Scheme $scheme; + } + + location /websocket { + proxy_pass http://apiserver/websocket; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $connection_upgrade; + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_read_timeout 86400; + } + + location /machine { + proxy_pass http://apiserver/machine; + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Scheme $scheme; + } + + location /server { + proxy_pass http://apiserver/server; + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Scheme $scheme; + } + + location /webcam/ { + proxy_pass http://mjpgstreamer/; + } +} \ No newline at end of file diff --git a/resources/mainsail_nginx.cfg b/resources/mainsail_nginx.cfg index 8d54ca3..4a3e93f 100644 --- a/resources/mainsail_nginx.cfg +++ b/resources/mainsail_nginx.cfg @@ -1,19 +1,4 @@ -map $http_upgrade $connection_upgrade { - default upgrade; - '' close; -} - -upstream apiserver { - #edit your api port here - ip_hash; - server 127.0.0.1:7125; -} - -upstream mjpgstreamer { - #edit your webcam port here - ip_hash; - server 127.0.0.1:8081; -} +# /etc/nginx/sites-available/mainsail server { listen 80; @@ -22,6 +7,16 @@ server { access_log /var/log/nginx/mainsail-access.log; error_log /var/log/nginx/mainsail-error.log; + #disable this section on smaller hardware like a pi zero + gzip on; + gzip_vary on; + gzip_proxied any; + gzip_proxied expired no-cache no-store private auth; + gzip_comp_level 4; + gzip_buffers 16 8k; + gzip_http_version 1.1; + gzip_types text/plain text/css text/xml text/javascript application/x-javascript application/json application/xml; + #web_path from mainsail static files root /home/pi/mainsail; diff --git a/resources/moonraker_nginx.cfg b/resources/moonraker_nginx.cfg new file mode 100644 index 0000000..c868181 --- /dev/null +++ b/resources/moonraker_nginx.cfg @@ -0,0 +1,13 @@ +# /etc/nginx/conf.d/upstreams.conf + +upstream apiserver { + #edit your api port here + ip_hash; + server 127.0.0.1:7125; +} + +upstream mjpgstreamer { + #edit your webcam port here + ip_hash; + server 127.0.0.1:8081; +} \ No newline at end of file diff --git a/scripts/install_dwc2.sh b/scripts/install_dwc2.sh index 789a4d4..84f09e2 100755 --- a/scripts/install_dwc2.sh +++ b/scripts/install_dwc2.sh @@ -1,6 +1,9 @@ install_dwc2(){ if [ -d $KLIPPER_DIR ]; then system_check_dwc2 + #check for other enabled web interfaces + unset SET_LISTEN_PORT + detect_enabled_sites #ask user for customization get_user_selections_dwc2 #dwc2 main installation @@ -10,7 +13,7 @@ install_dwc2(){ write_printer_cfg_dwc2 #execute customizations disable_octoprint - create_reverse_proxy "dwc2" + set_nginx_cfg "dwc2" set_hostname #after install actions restart_klipper @@ -31,7 +34,6 @@ system_check_dwc2(){ fi #check if octoprint is installed if systemctl is-enabled octoprint.service -q 2>/dev/null; then - unset OCTOPRINT_ENABLED OCTOPRINT_ENABLED="true" fi } @@ -104,9 +106,7 @@ get_user_selections_dwc2(){ #ask user to install reverse proxy dwc2_reverse_proxy_dialog #ask to change hostname - if [ "$SET_REVERSE_PROXY" = "true" ]; then - create_custom_hostname - fi + [ "$SET_NGINX_CFG" = "true" ] && create_custom_hostname #ask user to disable octoprint when such installed service was found if [ "$OCTOPRINT_ENABLED" = "true" ]; then unset DISABLE_OPRINT @@ -191,7 +191,7 @@ download_dwc2_webui(){ GET_DWC2_URL=`curl -s https://api.github.com/repositories/28820678/releases/latest | grep browser_download_url | cut -d'"' -f4` cd $DWC2_DIR status_msg "Downloading DWC2 Web UI ..." - wget $GET_DWC2_URL + wget -O $GET_DWC2_URL ok_msg "Download complete!" status_msg "Unzipping archive ..." unzip -q -o *.zip @@ -295,7 +295,6 @@ DEFAULT_DWC2_CFG ############################################################# dwc2_reverse_proxy_dialog(){ - unset SET_REVERSE_PROXY echo top_border echo -e "| If you want to have a nicer URL or simply need/want | " @@ -306,14 +305,53 @@ dwc2_reverse_proxy_dialog(){ read -p "${cyan}###### Do you want to set up a reverse proxy now? (y/N):${default} " yn case "$yn" in Y|y|Yes|yes) - SET_REVERSE_PROXY="true" + dwc2_port_check break;; N|n|No|no|"") - SET_REVERSE_PROXY="false" break;; *) print_unkown_cmd print_msg && clear_msg;; esac done +} + +dwc2_port_check(){ + if [ "$DWC2_ENABLED" = "false" ]; then + if [ "$SITE_ENABLED" = "true" ]; then + echo "Detected other enabled Interfaces:" + [ "$OCTOPRINT_ENABLED" = "true" ] && echo "${cyan}● OctoPrint - Port:$OCTOPRINT_PORT${default}" + [ "$MAINSAIL_ENABLED" = "true" ] && echo "${cyan}● Mainsail - Port:$MAINSAIL_PORT${default}" + [ "$FLUIDD_ENABLED" = "true" ] && echo "${cyan}● Fluidd - Port:$FLUIDD_PORT${default}" + if [ "$MAINSAIL_PORT" = "80" ] || [ "$OCTOPRINT_PORT" = "80" ] || [ "$FLUIDD_PORT" = "80" ]; then + PORT_80_BLOCKED="true" + fi + if [ "$PORT_80_BLOCKED" = "true" ]; then + [ "$OCTOPRINT_PORT" = "80" ] && echo "${cyan}OctoPrint${default} already listens on Port 80!" + [ "$MAINSAIL_PORT" = "80" ] && echo "${cyan}Mainsail${default} already listens on Port 80!" + [ "$FLUIDD_PORT" = "80" ] && echo "${cyan}Fluidd${default} already listens on Port 80!" + echo "You need to choose a different Port for DWC2 than the above!" + select_dwc2_port + fi + else + DEFAULT_PORT=$(grep listen ${SRCDIR}/kiauh/resources/dwc2_nginx.cfg | head -1 | sed 's/^\s*//' | cut -d" " -f2 | cut -d";" -f1) + SET_LISTEN_PORT=$DEFAULT_PORT + fi + SET_NGINX_CFG="true" + else + SET_NGINX_CFG="false" + fi +} + +select_dwc2_port(){ + while true; do + read -p "${cyan}Please enter a new Port:${default} " NEW_PORT + if [ "$NEW_PORT" != "$MAINSAIL_PORT" ] && [ "$NEW_PORT" != "$OCTOPRINT_PORT" ] && [ "$NEW_PORT" != "$FLUIDD_PORT" ]; then + echo "Setting port $NEW_PORT for DWC2!" + SET_LISTEN_PORT=$NEW_PORT + break + else + echo "That port is already taken! Select a different one!" + fi + done } \ No newline at end of file diff --git a/scripts/install_fluidd.sh b/scripts/install_fluidd.sh new file mode 100755 index 0000000..a7cd0c4 --- /dev/null +++ b/scripts/install_fluidd.sh @@ -0,0 +1,75 @@ +install_fluidd(){ + if [ "$INST_FLUIDD" = "true" ]; then + unset SET_LISTEN_PORT + #check for other enabled web interfaces + detect_enabled_sites + #check if another site already listens to port 80 + fluidd_port_check + #creating the fluidd nginx cfg + set_nginx_cfg "fluidd" + fluidd_setup && ok_msg "Fluidd installation complete!"; echo + fi +} + +fluidd_port_check(){ + if [ "$FLUIDD_ENABLED" = "false" ]; then + if [ "$SITE_ENABLED" = "true" ]; then + echo "Detected other enabled Interfaces:" + [ "$MAINSAIL_ENABLED" = "true" ] && echo "${cyan}● Mainsail - Port:$MAINSAIL_PORT${default}" + [ "$DWC2_ENABLED" = "true" ] && echo "${cyan}● DWC2 - Port:$DWC2_PORT${default}" + [ "$OCTOPRINT_ENABLED" = "true" ] && echo "${cyan}● OctoPrint - Port:$OCTOPRINT_PORT${default}" + if [ "$MAINSAIL_PORT" = "80" ] || [ "$DWC2_PORT" = "80" ] || [ "$OCTOPRINT_PORT" = "80" ]; then + PORT_80_BLOCKED="true" + fi + if [ "$PORT_80_BLOCKED" = "true" ]; then + [ "$MAINSAIL_PORT" = "80" ] && echo "${cyan}Mainsail${default} already listens on Port 80!" + [ "$DWC2_PORT" = "80" ] && echo "${cyan}DWC2${default} already listens on Port 80!" + [ "$OCTOPRINT_PORT" = "80" ] && echo "${cyan}OctoPrint${default} already listens on Port 80!" + echo "You need to choose a different Port for Fluidd than the above!" + select_fluidd_port + fi + else + DEFAULT_PORT=$(grep listen ${SRCDIR}/kiauh/resources/fluidd_nginx.cfg | head -1 | sed 's/^\s*//' | cut -d" " -f2 | cut -d";" -f1) + SET_LISTEN_PORT=$DEFAULT_PORT + fi + SET_NGINX_CFG="true" + else + SET_NGINX_CFG="false" + fi +} + +select_fluidd_port(){ + while true; do + read -p "${cyan}Please enter a new Port:${default} " NEW_PORT + if [ "$NEW_PORT" != "$MAINSAIL_PORT" ] && [ "$NEW_PORT" != "$DWC2_PORT" ] && [ "$NEW_PORT" != "$OCTOPRINT_PORT" ]; then + echo "Setting port $NEW_PORT for Mainsail!" + SET_LISTEN_PORT=$NEW_PORT + break + else + echo "That port is already taken! Select a different one!" + fi + done +} + +get_fluidd_ver(){ + FLUIDD_VERSION=$(curl -s https://api.github.com/repositories/295836951/tags | grep name | cut -d'"' -f4 | cut -d"v" -f2 | head -1) +} + +fluidd_dl_url(){ + get_fluidd_ver + FLUIDD_URL=https://github.com/cadriel/fluidd/releases/download/v$FLUIDD_VERSION/fluidd_v$FLUIDD_VERSION.zip +} + +fluidd_setup(){ + fluidd_dl_url + #clean up an existing fluidd folder + [ -d $FLUIDD_DIR ] && rm -rf $FLUIDD_DIR + #create fresh fluidd folder and download fluidd + mkdir $FLUIDD_DIR + cd $FLUIDD_DIR + status_msg "Downloading Fluidd $FLUIDD_VERSION ..." + wget -O fluidd.zip $FLUIDD_URL && status_msg "Extracting archive ..." && unzip -o fluidd.zip && rm fluidd.zip + ### write fluidd version to file for update check reasons + echo "$FLUIDD_VERSION" > $FLUIDD_DIR/version + echo +} \ No newline at end of file diff --git a/scripts/install_mainsail.sh b/scripts/install_mainsail.sh index 537995f..ae0a01a 100755 --- a/scripts/install_mainsail.sh +++ b/scripts/install_mainsail.sh @@ -1,47 +1,73 @@ install_mainsail(){ if [ "$INST_MAINSAIL" = "true" ]; then - unset SET_REVERSE_PROXY && SET_REVERSE_PROXY="true" #quick and dirty hack to make mainsail reverse proxy install, needs polish - create_reverse_proxy "mainsail" - mainsail_setup - test_nginx - ok_msg "Mainsail installation complete!"; echo + unset SET_LISTEN_PORT + #check for other enabled web interfaces + detect_enabled_sites + #check if another site already listens to port 80 + mainsail_port_check + #creating the mainsail nginx cfg + set_nginx_cfg "mainsail" + mainsail_setup && ok_msg "Mainsail installation complete!"; echo fi } -test_nginx(){ - HOST_IP=$(hostname -I | cut -d" " -f1) - status_msg "Testing Nginx ..." - sleep 5 - status_msg "API response from http://$HOST_IP/printer/info :" - API_RESPONSE="$(curl -sG4m5 http://$HOST_IP/printer/info)" - echo -e "${cyan}$API_RESPONSE${default}" - if [ $(curl -sG4 "http://$HOST_IP/printer/info" | grep '^{"result"' -c) -eq 1 ]; then - echo; ok_msg "Nginx is working correctly!"; echo +mainsail_port_check(){ + if [ "$MAINSAIL_ENABLED" = "false" ]; then + if [ "$SITE_ENABLED" = "true" ]; then + echo "Detected other enabled Interfaces:" + [ "$FLUIDD_ENABLED" = "true" ] && echo "${cyan}● Fluidd - Port:$FLUIDD_PORT${default}" + [ "$DWC2_ENABLED" = "true" ] && echo "${cyan}● DWC2 - Port:$DWC2_PORT${default}" + [ "$OCTOPRINT_ENABLED" = "true" ] && echo "${cyan}● OctoPrint - Port:$OCTOPRINT_PORT${default}" + if [ "$FLUIDD_PORT" = "80" ] || [ "$DWC2_PORT" = "80" ] || [ "$OCTOPRINT_PORT" = "80" ]; then + PORT_80_BLOCKED="true" + fi + if [ "$PORT_80_BLOCKED" = "true" ]; then + [ "$FLUIDD_PORT" = "80" ] && echo "${cyan}Fluidd${default} already listens on Port 80!" + [ "$DWC2_PORT" = "80" ] && echo "${cyan}DWC2${default} already listens on Port 80!" + [ "$OCTOPRINT_PORT" = "80" ] && echo "${cyan}OctoPrint${default} already listens on Port 80!" + echo "You need to choose a different Port for Mainsail than the above!" + select_mainsail_port + fi + else + DEFAULT_PORT=$(grep listen ${SRCDIR}/kiauh/resources/mainsail_nginx.cfg | head -1 | sed 's/^\s*//' | cut -d" " -f2 | cut -d";" -f1) + SET_LISTEN_PORT=$DEFAULT_PORT + fi + SET_NGINX_CFG="true" else - echo; warn_msg "Nginx is not working correctly!"; echo + SET_NGINX_CFG="false" fi } +select_mainsail_port(){ + while true; do + read -p "${cyan}Please enter a new Port:${default} " NEW_PORT + if [ "$NEW_PORT" != "$FLUIDD_PORT" ] && [ "$NEW_PORT" != "$DWC2_PORT" ] && [ "$NEW_PORT" != "$OCTOPRINT_PORT" ]; then + echo "Setting port $NEW_PORT for Mainsail!" + SET_LISTEN_PORT=$NEW_PORT + break + else + echo "That port is already taken! Select a different one!" + fi + done +} + get_mainsail_ver(){ MAINSAIL_VERSION=$(curl -s https://api.github.com/repositories/240875926/tags | grep name | cut -d'"' -f4 | cut -d"v" -f2 | head -1) } mainsail_dl_url(){ get_mainsail_ver - MAINSAIL_URL=https://github.com/meteyou/mainsail/releases/download/v"$MAINSAIL_VERSION"/mainsail-beta-"$MAINSAIL_VERSION".zip + MAINSAIL_URL=https://github.com/meteyou/mainsail/releases/download/v$MAINSAIL_VERSION/mainsail-beta-$MAINSAIL_VERSION.zip } mainsail_setup(){ mainsail_dl_url #clean up an existing mainsail folder - if [ -d $MAINSAIL_DIR ]; then - rm -rf $MAINSAIL_DIR - fi + [ -d $MAINSAIL_DIR ] && rm -rf $MAINSAIL_DIR #create fresh mainsail folder and download mainsail - mkdir $MAINSAIL_DIR - cd $MAINSAIL_DIR + mkdir $MAINSAIL_DIR && cd $MAINSAIL_DIR status_msg "Downloading Mainsail v$MAINSAIL_VERSION ..." - wget -q -O mainsail.zip $MAINSAIL_URL && status_msg "Extracting archive ..." && unzip -o mainsail.zip && rm mainsail.zip + wget -O mainsail.zip $MAINSAIL_URL && status_msg "Extracting archive ..." && unzip -o mainsail.zip && rm mainsail.zip ### write mainsail version to file for update check reasons echo "$MAINSAIL_VERSION" > $MAINSAIL_DIR/version echo diff --git a/scripts/install_moonraker.sh b/scripts/install_moonraker.sh index bc54555..c5fcf3f 100755 --- a/scripts/install_moonraker.sh +++ b/scripts/install_moonraker.sh @@ -19,7 +19,9 @@ install_moonraker(){ restart_moonraker restart_klipper test_api - install_mainsail + #test_nginx + #install_mainsail + #install_fluidd } system_check_moonraker(){ @@ -67,35 +69,42 @@ system_check_moonraker(){ get_user_selections_moonraker(){ #ask if moonraker only or moonraker + mainsail - while true; do - echo - top_border - echo -e "| Do you want to install Moonraker and Mainsail? |" - echo -e "| You can choose to install Moonraker only by answering |" - echo -e "| with 'No'. |" - hr - echo -e "| If you select 'Yes' please be aware that an existing |" - echo -e "| Mainsail installation will then be overwritten! |" - bottom_border - read -p "${cyan}###### Install Moonraker + Mainsail? (Y/n):${default} " yn - case "$yn" in - Y|y|Yes|yes|"") - echo -e "###### > Yes" - INST_MAINSAIL="true" - break;; - N|n|No|no) - echo -e "###### > No" - INST_MAINSAIL="false" - break;; - *) - print_unkown_cmd - print_msg && clear_msg;; - esac - done - #ask to change hostname if mainsail should be installed as well - if [ "$INST_MAINSAIL" = "true" ]; then - create_custom_hostname - fi + #while true; do + # echo + # top_border + # echo -e "| Install the Moonraker API only? |" + # blank_line + # echo -e "| You can choose to install Moonraker and one of the |" + # echo -e "| following web interfaces: |" + # echo -e "| 1) Mainsail |" + # echo -e "| 2) Fluidd |" + # hr + # echo -e "| If you want to install a web interface later, just |" + # echo -e "| press 'ENTER' to continue the Moonraker installation. |" + # bottom_border + # read -p "${cyan}Please choose:${default} " selection + # case "$selection" in + # "") + # echo -e "###### > Moonraker only" + # INST_NOUI="true" + # break;; + # 1) + # echo -e "###### > Moonraker + Mainsail" + # INST_MAINSAIL="true" + # break;; + # 2) + # echo -e "###### > Moonraker + Fluidd" + # INST_FLUIDD="true" + # break;; + # *) + # print_unkown_cmd + # print_msg && clear_msg;; + # esac + #done + ##ask to change hostname if mainsail should be installed as well + #if [ "$INST_MAINSAIL" = "true" ]; then + # create_custom_hostname + #fi #user selection for printer.cfg if [ "$PRINTER_CFG_FOUND" = "false" ]; then unset SEL_DEF_CFG @@ -252,7 +261,7 @@ get_user_selections_moonraker(){ echo -e "| |" echo -e "| 1) Remove packages (recommend) |" echo -e "| 2) Disable only (may cause issues) |" - echo -e "| ${red}3) Skip this step (not recommend)${default} |" + echo -e "| ${red}3) Skip this step (not recommended)${default} |" bottom_border read -p "${cyan}###### Please choose:${default} " action unset REMOVE_HAPROXY @@ -312,6 +321,8 @@ moonraker_setup(){ ok_msg "Download complete!" status_msg "Installing Moonraker ..." $MOONRAKER_DIR/scripts/install-moonraker.sh + #copy moonraker configuration for nginx to /etc/nginx/conf.d + setup_moonraker_nginx_cfg #backup a possible existing printer.cfg at the old location #and before patching in the new location backup_printer_cfg @@ -480,6 +491,15 @@ setup_moonraker_conf(){ fi } +setup_moonraker_nginx_cfg(){ + if [ ! -f $NGINX_CONFD/upstreams.conf ]; then + sudo cp ${SRCDIR}/kiauh/resources/moonraker_nginx.cfg $NGINX_CONFD/upstreams.conf + fi + if [ ! -f $NGINX_CONFD/common_vars.conf ]; then + sudo cp ${SRCDIR}/kiauh/resources/common_vars_nginx.cfg $NGINX_CONFD/common_vars.conf + fi +} + ############################################################# ############################################################# @@ -661,3 +681,17 @@ test_api(){ echo; warn_msg "Klipper API not working correctly!"; echo fi } + +#test_nginx(){ +# HOST_IP=$(hostname -I | cut -d" " -f1) +# status_msg "Testing Nginx ..." +# sleep 5 +# status_msg "API response from http://$HOST_IP/printer/info :" +# API_RESPONSE="$(curl -sG4m5 http://$HOST_IP/printer/info)" +# echo -e "${cyan}$API_RESPONSE${default}" +# if [ $(curl -sG4 "http://$HOST_IP/printer/info" | grep '^{"result"' -c) -eq 1 #]; then +# echo; ok_msg "Nginx is working correctly!"; echo +# else +# echo; warn_msg "Nginx is not working correctly!"; echo +# fi +#} \ No newline at end of file diff --git a/scripts/install_octoprint.sh b/scripts/install_octoprint.sh index 0df8495..90e956d 100755 --- a/scripts/install_octoprint.sh +++ b/scripts/install_octoprint.sh @@ -1,4 +1,7 @@ install_octoprint(){ + #check for other enabled web interfaces + unset SET_LISTEN_PORT + detect_enabled_sites #ask user for customization get_user_selections_octoprint #octoprint main installation @@ -9,7 +12,7 @@ install_octoprint(){ add_reboot_permission create_config_yaml #execute customizations - create_reverse_proxy "octoprint" + set_nginx_cfg "octoprint" set_hostname #after install actions load_octoprint_server @@ -20,9 +23,7 @@ get_user_selections_octoprint(){ #ask user to set a reverse proxy octoprint_reverse_proxy_dialog #ask to change hostname - if [ "$SET_REVERSE_PROXY" = "true" ]; then - create_custom_hostname - fi + [ "$SET_NGINX_CFG" = "true" ] && create_custom_hostname status_msg "Installation will start now! Please wait ..." } @@ -113,7 +114,6 @@ add_reboot_permission(){ } octoprint_reverse_proxy_dialog(){ - unset SET_REVERSE_PROXY echo top_border echo -e "| If you want to have nicer URLs or simply need | " @@ -128,10 +128,9 @@ octoprint_reverse_proxy_dialog(){ echo -e "${default}" case "$yn" in Y|y|Yes|yes) - SET_REVERSE_PROXY="true" + octoprint_port_check break;; N|n|No|no|"") - SET_REVERSE_PROXY="false" break;; *) print_unkown_cmd @@ -140,6 +139,46 @@ octoprint_reverse_proxy_dialog(){ done } +octoprint_port_check(){ + if [ "$OCTOPRINT_ENABLED" = "false" ]; then + if [ "$SITE_ENABLED" = "true" ]; then + echo "Detected other enabled Interfaces:" + [ "$MAINSAIL_ENABLED" = "true" ] && echo "${cyan}● Mainsail - Port:$MAINSAIL_PORT${default}" + [ "$FLUIDD_ENABLED" = "true" ] && echo "${cyan}● Fluidd - Port:$FLUIDD_PORT${default}" + [ "$DWC2_ENABLED" = "true" ] && echo "${cyan}● DWC2 - Port:$DWC2_PORT${default}" + if [ "$MAINSAIL_PORT" = "80" ] || [ "$DWC2_PORT" = "80" ] || [ "$FLUIDD_PORT" = "80" ]; then + PORT_80_BLOCKED="true" + fi + if [ "$PORT_80_BLOCKED" = "true" ]; then + [ "$MAINSAIL_PORT" = "80" ] && echo "${cyan}Mainsail${default} already listens on Port 80!" + [ "$FLUIDD_PORT" = "80" ] && echo "${cyan}Fluidd${default} already listens on Port 80!" + [ "$DWC2_PORT" = "80" ] && echo "${cyan}DWC2${default} already listens on Port 80!" + echo "You need to choose a different Port for OctoPrint than the above!" + select_octoprint_port + fi + else + DEFAULT_PORT=$(grep listen ${SRCDIR}/kiauh/resources/octoprint_nginx.cfg | head -1 | sed 's/^\s*//' | cut -d" " -f2 | cut -d";" -f1) + SET_LISTEN_PORT=$DEFAULT_PORT + fi + SET_NGINX_CFG="true" + else + SET_NGINX_CFG="false" + fi +} + +select_octoprint_port(){ + while true; do + read -p "${cyan}Please enter a new Port:${default} " NEW_PORT + if [ "$NEW_PORT" != "$MAINSAIL_PORT" ] && [ "$NEW_PORT" != "$DWC2_PORT" ] && [ "$NEW_PORT" != "$FLUIDD_PORT" ]; then + echo "Setting port $NEW_PORT for OctoPrint!" + SET_LISTEN_PORT=$NEW_PORT + break + else + echo "That port is already taken! Select a different one!" + fi + done +} + create_config_yaml(){ if [ ! -d $OCTOPRINT_CFG_DIR ]; then status_msg "Creating config.yaml ..." diff --git a/scripts/set_reverse_proxy.sh b/scripts/network_functions.sh similarity index 58% rename from scripts/set_reverse_proxy.sh rename to scripts/network_functions.sh index f024801..4d72664 100755 --- a/scripts/set_reverse_proxy.sh +++ b/scripts/network_functions.sh @@ -1,29 +1,81 @@ -create_reverse_proxy(){ - if [ "$SET_REVERSE_PROXY" = "true" ]; then +set_nginx_cfg(){ + if [ "$SET_NGINX_CFG" = "true" ]; then #check for dependencies dep=(nginx) dependency_check #execute operations status_msg "Creating Nginx configuration for $1 ..." - cat ${HOME}/kiauh/resources/$1_nginx.cfg > ${HOME}/kiauh/resources/$1 - sudo mv ${HOME}/kiauh/resources/$1 /etc/nginx/sites-available/$1 - #ONLY FOR MAINSAIL: replace username if not "pi" - if [ "$1" = "mainsail" ]; then - sudo sed -i "/root/s/pi/${USER}/" /etc/nginx/sites-available/mainsail - fi + #copy content from resources to the respective nginx config file + cat ${SRCDIR}/kiauh/resources/$1_nginx.cfg > ${SRCDIR}/kiauh/resources/$1 + ##edit the nginx config file before moving it + if [ "$SET_LISTEN_PORT" != "$DEFAULT_PORT" ]; then + status_msg "Configuring port for $1 ..." + #set listen port ipv4 + sed -i "s/listen\s[0-9]*;/listen $SET_LISTEN_PORT;/" ${SRCDIR}/kiauh/resources/$1 + #set listen port ipv6 + sed -i "s/listen\s\[\:*\]\:[0-9]*;/listen \[::\]\:$SET_LISTEN_PORT;/" ${SRCDIR}/kiauh/resources/$1 + fi + #set correct user + if [ "$1" = "mainsail" ] || [ "$1" = "fluidd" ]; then + sudo sed -i "/root/s/pi/${USER}/" ${SRCDIR}/kiauh/resources/$1 + fi + #moving the config file into correct directory + sudo mv ${SRCDIR}/kiauh/resources/$1 /etc/nginx/sites-available/$1 ok_msg "Nginx configuration for $1 was set!" - #remove default config - if [ -e /etc/nginx/sites-enabled/default ]; then - sudo rm /etc/nginx/sites-enabled/default - fi - #create symlink for own configs - if [ ! -e /etc/nginx/sites-enabled/$1 ]; then - sudo ln -s /etc/nginx/sites-available/$1 /etc/nginx/sites-enabled/ + if [ "$SET_LISTEN_PORT" != "" ]; then + ok_msg "$1 listening on port $SET_LISTEN_PORT!" + else + ok_msg "$1 listening on def-port $DEFAULT_PORT!" fi + #remove nginx default config + [ -e /etc/nginx/sites-enabled/default ] && sudo rm /etc/nginx/sites-enabled/default + #create symlink for own sites + [ ! -e /etc/nginx/sites-enabled/$1 ] && sudo ln -s /etc/nginx/sites-available/$1 /etc/nginx/sites-enabled/ restart_nginx fi } +read_listen_port(){ + LISTEN_PORT=$(grep listen /etc/nginx/sites-enabled/$1 | head -1 | sed 's/^\s*//' | cut -d" " -f2 | cut -d";" -f1) +} + +detect_enabled_sites(){ + #check if there is another UI config already installed + #and reads the port they are listening on + if [ -e /etc/nginx/sites-enabled/mainsail ]; then + SITE_ENABLED="true" && MAINSAIL_ENABLED="true" + read_listen_port "mainsail" + MAINSAIL_PORT=$LISTEN_PORT + #echo "debug: Mainsail listens on port: $MAINSAIL_PORT" + else + MAINSAIL_ENABLED="false" + fi + if [ -e /etc/nginx/sites-enabled/fluidd ]; then + SITE_ENABLED="true" && FLUIDD_ENABLED="true" + read_listen_port "fluidd" + FLUIDD_PORT=$LISTEN_PORT + #echo "debug: Fluidd listens on port: $FLUIDD_PORT" + else + FLUIDD_ENABLED="false" + fi + if [ -e /etc/nginx/sites-enabled/dwc2 ]; then + SITE_ENABLED="true" && DWC2_ENABLED="true" + read_listen_port "dwc2" + DWC2_PORT=$LISTEN_PORT + #echo "debug: DWC2 listens on port: $DWC2_PORT" + else + DWC2_ENABLED="false" + fi + if [ -e /etc/nginx/sites-enabled/octoprint ]; then + SITE_ENABLED="true" && OCTOPRINT_ENABLED="true" + read_listen_port "octoprint" + OCTOPRINT_PORT=$LISTEN_PORT + #echo "debug: OctoPrint listens on port: $OCTOPRINT_PORT" + else + OCTOPRINT_ENABLED="false" + fi +} + create_custom_hostname(){ echo top_border diff --git a/scripts/remove.sh b/scripts/remove.sh index 489be4f..87885ac 100755 --- a/scripts/remove.sh +++ b/scripts/remove.sh @@ -86,6 +86,8 @@ remove_moonraker(){ $MOONRAKER_SERVICE2 $MOONRAKER_DIR $MOONRAKER_ENV_DIR + $NGINX_CONFD/upstreams.conf + $NGINX_CONFD/common_vars.conf ${HOME}/moonraker.conf ${HOME}/moonraker.log ${HOME}/klipper_config/moonraker.log @@ -139,6 +141,11 @@ remove_moonraker(){ rm -rf ${HOME}/moonraker.log ${HOME}/klipper_config/moonraker.log ${HOME}/klipper_config/klippy.log /tmp/moonraker.log ok_msg "Files removed!" fi + #remove moonraker nginx config + 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 #remove legacy api key if [ -e ${HOME}/.klippy_api_key ]; then status_msg "Removing legacy API Key ..." @@ -183,6 +190,33 @@ remove_mainsail(){ fi } +remove_fluidd(){ + data_arr=( + $fluidd_DIR + /etc/nginx/sites-available/fluidd + /etc/nginx/sites-enabled/fluidd + ) + print_error "Fluidd" && data_count=() + if [ "$ERROR_MSG" = "" ]; then + #remove fluidd dir + if [ -d $FLUIDD_DIR ]; then + status_msg "Removing Fluidd directory ..." + rm -rf $FLUIDD_DIR && ok_msg "Directory removed!" + fi + #remove fluidd config for nginx + if [ -e /etc/nginx/sites-available/fluidd ]; then + status_msg "Removing Fluidd configuration for Nginx ..." + sudo rm /etc/nginx/sites-available/fluidd && ok_msg "File removed!" + fi + #remove fluidd symlink for nginx + if [ -L /etc/nginx/sites-enabled/fluidd ]; then + status_msg "Removing Fluidd Symlink for Nginx ..." + sudo rm /etc/nginx/sites-enabled/fluidd && ok_msg "File removed!" + fi + CONFIRM_MSG="Fluidd successfully removed!" + fi +} + ############################################################# ############################################################# diff --git a/scripts/status.sh b/scripts/status.sh index 8f79f4b..a772056 100755 --- a/scripts/status.sh +++ b/scripts/status.sh @@ -258,6 +258,35 @@ compare_dwc2_versions(){ ############################################################# ############################################################# +read_moonraker_versions(){ + if [ -d $MOONRAKER_DIR ] && [ -d $MOONRAKER_DIR/.git ]; then + cd $MOONRAKER_DIR + git fetch origin master -q + LOCAL_MOONRAKER_COMMIT=$(git rev-parse --short=8 HEAD) + REMOTE_MOONRAKER_COMMIT=$(git rev-parse --short=8 origin/master) + else + LOCAL_MOONRAKER_COMMIT="${red}--------${default}" + REMOTE_MOONRAKER_COMMIT="${red}--------${default}" + fi +} + +compare_moonraker_versions(){ + unset MOONRAKER_UPDATE_AVAIL + read_moonraker_versions + #echo "Local: $LOCAL_MOONRAKER_COMMIT" + #echo "Remote: $REMOTE_MOONRAKER_COMMIT" + if [ "$LOCAL_MOONRAKER_COMMIT" != "$REMOTE_MOONRAKER_COMMIT" ]; then + LOCAL_MOONRAKER_COMMIT="${yellow}$LOCAL_MOONRAKER_COMMIT${default}" + REMOTE_MOONRAKER_COMMIT="${green}$REMOTE_MOONRAKER_COMMIT${default}" + MOONRAKER_UPDATE_AVAIL="true" + update_arr+=(update_moonraker) + else + LOCAL_MOONRAKER_COMMIT="${green}$LOCAL_MOONRAKER_COMMIT${default}" + REMOTE_MOONRAKER_COMMIT="${green}$REMOTE_MOONRAKER_COMMIT${default}" + MOONRAKER_UPDATE_AVAIL="false" + fi +} + read_local_mainsail_version(){ unset MAINSAIL_IS_INSTALLED if [ -e $MAINSAIL_DIR/version ]; then @@ -297,35 +326,48 @@ compare_mainsail_versions(){ fi } -read_moonraker_versions(){ - if [ -d $MOONRAKER_DIR ] && [ -d $MOONRAKER_DIR/.git ]; then - cd $MOONRAKER_DIR - git fetch origin master -q - LOCAL_MOONRAKER_COMMIT=$(git rev-parse --short=8 HEAD) - REMOTE_MOONRAKER_COMMIT=$(git rev-parse --short=8 origin/master) +read_local_fluidd_version(){ + unset FLUIDD_IS_INSTALLED + if [ -e $FLUIDD_DIR/version ]; then + FLUIDD_LOCAL_VER=$(head -n 1 $FLUIDD_DIR/version) + FLUIDD_IS_INSTALLED="true" else - LOCAL_MOONRAKER_COMMIT="${red}--------${default}" - REMOTE_MOONRAKER_COMMIT="${red}--------${default}" + FLUIDD_LOCAL_VER="${red}-----${default}" + FLUIDD_IS_INSTALLED="false" fi } -compare_moonraker_versions(){ - unset MOONRAKER_UPDATE_AVAIL - read_moonraker_versions - #echo "Local: $LOCAL_MOONRAKER_COMMIT" - #echo "Remote: $REMOTE_MOONRAKER_COMMIT" - if [ "$LOCAL_MOONRAKER_COMMIT" != "$REMOTE_MOONRAKER_COMMIT" ]; then - LOCAL_MOONRAKER_COMMIT="${yellow}$LOCAL_MOONRAKER_COMMIT${default}" - REMOTE_MOONRAKER_COMMIT="${green}$REMOTE_MOONRAKER_COMMIT${default}" - MOONRAKER_UPDATE_AVAIL="true" - update_arr+=(update_moonraker) +read_remote_fluidd_version(){ + #remote checks don't work without curl installed! + if [[ ! $(dpkg-query -f'${Status}' --show curl 2>/dev/null) = *\ installed ]]; then + FLUIDD_REMOTE_VER="${red}-----${default}" else - LOCAL_MOONRAKER_COMMIT="${green}$LOCAL_MOONRAKER_COMMIT${default}" - REMOTE_MOONRAKER_COMMIT="${green}$REMOTE_MOONRAKER_COMMIT${default}" - MOONRAKER_UPDATE_AVAIL="false" + get_fluidd_ver + FLUIDD_REMOTE_VER=$FLUIDD_VERSION fi } +compare_fluidd_versions(){ + unset FLUIDD_UPDATE_AVAIL + read_local_fluidd_version + read_remote_fluidd_version + #echo "Local: $FLUIDD_LOCAL_VER" + #echo "Remote: $FLUIDD_REMOTE_VER" + if [ "$FLUIDD_LOCAL_VER" != "$FLUIDD_REMOTE_VER" ] && [ "$FLUIDD_IS_INSTALLED" = "true" ]; then + FLUIDD_LOCAL_VER="${yellow}$FLUIDD_LOCAL_VER${default}" + FLUIDD_REMOTE_VER="${green}$FLUIDD_REMOTE_VER${default}" + FLUIDD_UPDATE_AVAIL="true" + update_arr+=(update_fluidd) + else + FLUIDD_LOCAL_VER="${green}$FLUIDD_LOCAL_VER${default}" + FLUIDD_REMOTE_VER="${green}$FLUIDD_REMOTE_VER${default}" + FLUIDD_UPDATE_AVAIL="false" + fi +} + +############################################################# +############################################################# + ui_print_versions(){ unset update_arr compare_klipper_versions @@ -333,4 +375,5 @@ ui_print_versions(){ compare_dwc2_versions compare_moonraker_versions compare_mainsail_versions + compare_fluidd_versions } diff --git a/scripts/ui/general_ui.sh b/scripts/ui/general_ui.sh index 70e26cf..f9aa137 100755 --- a/scripts/ui/general_ui.sh +++ b/scripts/ui/general_ui.sh @@ -7,6 +7,10 @@ bottom_border(){ echo -e "\=======================================================/" } +blank_line(){ + echo -e "| | " +} + hr(){ echo -e "|-------------------------------------------------------|" } @@ -29,7 +33,7 @@ kiauh_update_msg(){ top_border echo -e "| ${yellow}There is a newer version of this script available!${default} | " echo -e "| ${yellow}Type 'update' if you want to update KIAUH now.${default} | " - echo -e "| | " + blank_line echo -e "| ${yellow}Check out the KIAUH changelog for important changes${default} | " echo -e "| ${yellow}either to the script or the installable components!${default} | " bottom_border diff --git a/scripts/ui/install_menu.sh b/scripts/ui/install_menu.sh index 8e0ec75..3b0352a 100755 --- a/scripts/ui/install_menu.sh +++ b/scripts/ui/install_menu.sh @@ -9,8 +9,8 @@ install_ui(){ echo -e "| Firmware: | Webinterface: | " echo -e "| 1) [Klipper] | 3) [DWC2] | " echo -e "| | 4) [Mainsail] | " - echo -e "| Klipper API: | 5) [Octoprint] | " - echo -e "| 2) [Moonraker] | | " + echo -e "| Klipper API: | 5) [Fluidd] | " + echo -e "| 2) [Moonraker] | 6) [Octoprint] | " quit_footer } @@ -47,6 +47,12 @@ install_menu(){ print_msg && clear_msg install_ui;; 5) + clear + print_header + INST_FLUIDD="true" && install_fluidd + print_msg && clear_msg + install_ui;; + 6) clear print_header install_octoprint diff --git a/scripts/ui/main_menu.sh b/scripts/ui/main_menu.sh index 07d86c4..0844dec 100755 --- a/scripts/ui/main_menu.sh +++ b/scripts/ui/main_menu.sh @@ -32,6 +32,7 @@ main_menu(){ read -p "Perform action: " action; echo echo -e "${default}" case "$action" in + 8) read_listen_port;; update) clear print_header diff --git a/scripts/ui/remove_menu.sh b/scripts/ui/remove_menu.sh index f0692b5..efa91bb 100755 --- a/scripts/ui/remove_menu.sh +++ b/scripts/ui/remove_menu.sh @@ -11,10 +11,11 @@ remove_ui(){ echo -e "| Firmware: | Webinterface: | " echo -e "| 1) [Klipper] | 3) [DWC2] | " echo -e "| | 4) [Mainsail] | " - echo -e "| Klipper API: | 5) [Octoprint] | " - echo -e "| 2) [Moonraker] | | " + echo -e "| Klipper API: | 5) [Fluidd] | " + echo -e "| 2) [Moonraker] | 6) [Octoprint] | " + echo -e "| | | " echo -e "| | Webserver: | " - echo -e "| | 6) [Nginx] | " + echo -e "| | 7) [Nginx] | " quit_footer } @@ -53,10 +54,16 @@ remove_menu(){ 5) clear print_header - remove_octoprint + remove_fluidd print_msg && clear_msg remove_ui;; 6) + clear + print_header + remove_octoprint + print_msg && clear_msg + remove_ui;; + 7) clear print_header remove_nginx diff --git a/scripts/ui/update_menu.sh b/scripts/ui/update_menu.sh index 9834a3b..629da4e 100755 --- a/scripts/ui/update_menu.sh +++ b/scripts/ui/update_menu.sh @@ -21,6 +21,7 @@ update_ui(){ echo -e "| |---------------|--------------| " echo -e "| 4) [Moonraker] | $(echo "$LOCAL_MOONRAKER_COMMIT") | $(echo "$REMOTE_MOONRAKER_COMMIT") | " echo -e "| 5) [Mainsail] | $(echo "$MAINSAIL_LOCAL_VER") | $(echo "$MAINSAIL_REMOTE_VER") | " + echo -e "| 6) [Fluidd] | $(echo "$FLUIDD_LOCAL_VER") | $(echo "$FLUIDD_REMOTE_VER") | " quit_footer } From 4e38e8596282852af522a34ab28f55c120cd84f4 Mon Sep 17 00:00:00 2001 From: th33xitus Date: Tue, 6 Oct 2020 17:55:24 +0200 Subject: [PATCH 04/12] bugfix: add greater delay to test_api to let cpu usage settle on slower devices --- scripts/install_moonraker.sh | 35 +++++++++++++++-------------------- 1 file changed, 15 insertions(+), 20 deletions(-) diff --git a/scripts/install_moonraker.sh b/scripts/install_moonraker.sh index c5fcf3f..8296e7d 100755 --- a/scripts/install_moonraker.sh +++ b/scripts/install_moonraker.sh @@ -671,27 +671,22 @@ handle_haproxy_lighttpd(){ test_api(){ HOST_IP=$(hostname -I | cut -d" " -f1) status_msg "Testing API ..." - sleep 5 - status_msg "API response from http://$HOST_IP:7125/printer/info :" - API_RESPONSE=$(curl -sG4m5 http://$HOST_IP:7125/printer/info) - echo -e "${cyan}$API_RESPONSE${default}" - if [ $(curl -sG4 "http://$HOST_IP:7125/printer/info" | grep '^{"result"' -c) -eq 1 ]; then + status_msg "Please wait ..." + sleep 15 + status_msg "API response from http://"$HOST_IP":7125/printer/info :" + echo -e "${cyan}$(curl -s "http://"$HOST_IP":7125/printer/info")${default}" + if [ $(curl -s "http://"$HOST_IP":7125/printer/info" | grep '^{"result"' -c) -eq 1 ]; then echo; ok_msg "Klipper API is working correctly!"; echo else echo; warn_msg "Klipper API not working correctly!"; echo fi -} - -#test_nginx(){ -# HOST_IP=$(hostname -I | cut -d" " -f1) -# status_msg "Testing Nginx ..." -# sleep 5 -# status_msg "API response from http://$HOST_IP/printer/info :" -# API_RESPONSE="$(curl -sG4m5 http://$HOST_IP/printer/info)" -# echo -e "${cyan}$API_RESPONSE${default}" -# if [ $(curl -sG4 "http://$HOST_IP/printer/info" | grep '^{"result"' -c) -eq 1 #]; then -# echo; ok_msg "Nginx is working correctly!"; echo -# else -# echo; warn_msg "Nginx is not working correctly!"; echo -# fi -#} \ No newline at end of file + status_msg "Testing Nginx ..." + status_msg "Please wait ..." + status_msg "API response from http://"$HOST_IP"/printer/info :" + echo -e "${cyan}$(curl -s "http://"$HOST_IP"/printer/info")${default}" + if [ $(curl -s "http://"$HOST_IP"/printer/info" | grep '^{"result"' -c) -eq 1 ]; then + echo; ok_msg "Nginx is working correctly!"; echo + else + echo; warn_msg "Nginx is not working correctly!"; echo + fi +} \ No newline at end of file From 3658137a1cafe9730b344ff7303b563e1d76adb3 Mon Sep 17 00:00:00 2001 From: th33xitus Date: Tue, 6 Oct 2020 18:18:40 +0200 Subject: [PATCH 05/12] bugfix: fix introduced bug in fe941f4 --- scripts/install_dwc2.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/install_dwc2.sh b/scripts/install_dwc2.sh index 84f09e2..3ba1ef2 100755 --- a/scripts/install_dwc2.sh +++ b/scripts/install_dwc2.sh @@ -188,10 +188,10 @@ patch_klipper_sysfile_dwc2(){ download_dwc2_webui(){ #get Duet Web Control - GET_DWC2_URL=`curl -s https://api.github.com/repositories/28820678/releases/latest | grep browser_download_url | cut -d'"' -f4` + GET_DWC2_URL=$(curl -s https://api.github.com/repositories/28820678/releases/latest | grep browser_download_url | cut -d'"' -f4) cd $DWC2_DIR status_msg "Downloading DWC2 Web UI ..." - wget -O $GET_DWC2_URL + wget $GET_DWC2_URL ok_msg "Download complete!" status_msg "Unzipping archive ..." unzip -q -o *.zip From 9b424c8343f9eb519ffd81613048cc3408f3b974 Mon Sep 17 00:00:00 2001 From: th33xitus Date: Tue, 6 Oct 2020 18:33:02 +0200 Subject: [PATCH 06/12] bugfix: add missing removal actions --- scripts/remove.sh | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/scripts/remove.sh b/scripts/remove.sh index 87885ac..e91622c 100755 --- a/scripts/remove.sh +++ b/scripts/remove.sh @@ -38,6 +38,8 @@ remove_dwc2(){ $DWC2FK_DIR $DWC_ENV_DIR $DWC2_DIR + /etc/nginx/sites-available/dwc2 + /etc/nginx/sites-enabled/dwc2 ) print_error "DWC2-for-Klipper-Socket &\n DWC2 Web UI" && data_count=() if [ "$ERROR_MSG" = "" ]; then @@ -73,6 +75,16 @@ remove_dwc2(){ status_msg "Removing DWC2 directory ..." rm -rf $DWC2_DIR && ok_msg "Directory removed!" fi + #remove dwc2 config for nginx + if [ -e /etc/nginx/sites-available/dwc2 ]; then + status_msg "Removing DWC2 configuration for Nginx ..." + sudo rm /etc/nginx/sites-available/dwc2 && ok_msg "File removed!" + fi + #remove dwc2 symlink for nginx + if [ -L /etc/nginx/sites-enabled/dwc2 ]; then + status_msg "Removing DWC2 Symlink for Nginx ..." + sudo rm /etc/nginx/sites-enabled/dwc2 && ok_msg "File removed!" + fi CONFIRM_MSG=" DWC2-for-Klipper-Socket & DWC2 successfully removed!" fi } @@ -250,6 +262,16 @@ remove_octoprint(){ status_msg "Removing octoprint.log Symlink ..." rm -rf ${HOME}/octoprint.log && ok_msg "Symlink removed!" fi + #remove octoprint config for nginx + if [ -e /etc/nginx/sites-available/octoprint ]; then + status_msg "Removing OctoPrint configuration for Nginx ..." + sudo rm /etc/nginx/sites-available/octoprint && ok_msg "File removed!" + fi + #remove octoprint symlink for nginx + if [ -L /etc/nginx/sites-enabled/octoprint ]; then + status_msg "Removing OctoPrint Symlink for Nginx ..." + sudo rm /etc/nginx/sites-enabled/octoprint && ok_msg "File removed!" + fi CONFIRM_MSG=" OctoPrint successfully removed!" fi } From 029127bf00ae29d14c84d4692ed137fee0fe1634 Mon Sep 17 00:00:00 2001 From: th33xitus Date: Tue, 6 Oct 2020 20:19:37 +0200 Subject: [PATCH 07/12] fix: nginx port stuff on all interface installers --- scripts/install_dwc2.sh | 52 ++++++++++++++++++++++-------------- scripts/install_fluidd.sh | 52 ++++++++++++++++++++++-------------- scripts/install_mainsail.sh | 52 ++++++++++++++++++++++-------------- scripts/install_octoprint.sh | 52 ++++++++++++++++++++++-------------- 4 files changed, 128 insertions(+), 80 deletions(-) diff --git a/scripts/install_dwc2.sh b/scripts/install_dwc2.sh index 3ba1ef2..17e299d 100755 --- a/scripts/install_dwc2.sh +++ b/scripts/install_dwc2.sh @@ -319,18 +319,12 @@ dwc2_reverse_proxy_dialog(){ dwc2_port_check(){ if [ "$DWC2_ENABLED" = "false" ]; then if [ "$SITE_ENABLED" = "true" ]; then - echo "Detected other enabled Interfaces:" - [ "$OCTOPRINT_ENABLED" = "true" ] && echo "${cyan}● OctoPrint - Port:$OCTOPRINT_PORT${default}" - [ "$MAINSAIL_ENABLED" = "true" ] && echo "${cyan}● Mainsail - Port:$MAINSAIL_PORT${default}" - [ "$FLUIDD_ENABLED" = "true" ] && echo "${cyan}● Fluidd - Port:$FLUIDD_PORT${default}" + status_msg "Detected other enabled interfaces:" + [ "$OCTOPRINT_ENABLED" = "true" ] && echo " ${cyan}● OctoPrint - Port:$OCTOPRINT_PORT${default}" + [ "$MAINSAIL_ENABLED" = "true" ] && echo " ${cyan}● Mainsail - Port:$MAINSAIL_PORT${default}" + [ "$FLUIDD_ENABLED" = "true" ] && echo " ${cyan}● Fluidd - Port:$FLUIDD_PORT${default}" if [ "$MAINSAIL_PORT" = "80" ] || [ "$OCTOPRINT_PORT" = "80" ] || [ "$FLUIDD_PORT" = "80" ]; then PORT_80_BLOCKED="true" - fi - if [ "$PORT_80_BLOCKED" = "true" ]; then - [ "$OCTOPRINT_PORT" = "80" ] && echo "${cyan}OctoPrint${default} already listens on Port 80!" - [ "$MAINSAIL_PORT" = "80" ] && echo "${cyan}Mainsail${default} already listens on Port 80!" - [ "$FLUIDD_PORT" = "80" ] && echo "${cyan}Fluidd${default} already listens on Port 80!" - echo "You need to choose a different Port for DWC2 than the above!" select_dwc2_port fi else @@ -344,14 +338,32 @@ dwc2_port_check(){ } select_dwc2_port(){ - while true; do - read -p "${cyan}Please enter a new Port:${default} " NEW_PORT - if [ "$NEW_PORT" != "$MAINSAIL_PORT" ] && [ "$NEW_PORT" != "$OCTOPRINT_PORT" ] && [ "$NEW_PORT" != "$FLUIDD_PORT" ]; then - echo "Setting port $NEW_PORT for DWC2!" - SET_LISTEN_PORT=$NEW_PORT - break - else - echo "That port is already taken! Select a different one!" - fi - done + if [ "$PORT_80_BLOCKED" = "true" ]; then + echo + top_border + echo -e "| ${red}!!!WARNING!!!${default} |" + echo -e "| ${red}You need to choose a different port for DWC2!${default} |" + echo -e "| ${red}The following web interface is listening at port 80:${default} |" + blank_line + [ "$OCTOPRINT_PORT" = "80" ] && echo "| ● OctoPrint |" + [ "$MAINSAIL_PORT" = "80" ] && echo "| ● Mainsail |" + [ "$FLUIDD_PORT" = "80" ] && echo "| ● Fluidd |" + blank_line + echo -e "| Make sure you don't choose a port which was already |" + echo -e "| assigned to one of the other web interfaces! |" + blank_line + echo -e "| Be aware: there is ${red}NO${default} sanity check for the following |" + echo -e "| input. So make sure to choose a valid port! |" + bottom_border + while true; do + read -p "${cyan}Please enter a new Port:${default} " NEW_PORT + if [ "$NEW_PORT" != "$MAINSAIL_PORT" ] && [ "$NEW_PORT" != "$FLUIDD_PORT" ] && [ "$NEW_PORT" != "$OCTOPRINT_PORT" ]; then + echo "Setting port $NEW_PORT for DWC2!" + SET_LISTEN_PORT=$NEW_PORT + break + else + echo "That port is already taken! Select a different one!" + fi + done + fi } \ No newline at end of file diff --git a/scripts/install_fluidd.sh b/scripts/install_fluidd.sh index a7cd0c4..5df8e08 100755 --- a/scripts/install_fluidd.sh +++ b/scripts/install_fluidd.sh @@ -14,18 +14,12 @@ install_fluidd(){ fluidd_port_check(){ if [ "$FLUIDD_ENABLED" = "false" ]; then if [ "$SITE_ENABLED" = "true" ]; then - echo "Detected other enabled Interfaces:" - [ "$MAINSAIL_ENABLED" = "true" ] && echo "${cyan}● Mainsail - Port:$MAINSAIL_PORT${default}" - [ "$DWC2_ENABLED" = "true" ] && echo "${cyan}● DWC2 - Port:$DWC2_PORT${default}" - [ "$OCTOPRINT_ENABLED" = "true" ] && echo "${cyan}● OctoPrint - Port:$OCTOPRINT_PORT${default}" + status_msg "Detected other enabled interfaces:" + [ "$OCTOPRINT_ENABLED" = "true" ] && echo " ${cyan}● OctoPrint - Port: $OCTOPRINT_PORT${default}" + [ "$MAINSAIL_ENABLED" = "true" ] && echo " ${cyan}● Mainsail - Port: $MAINSAIL_PORT${default}" + [ "$DWC2_ENABLED" = "true" ] && echo " ${cyan}● DWC2 - Port: $DWC2_PORT${default}" if [ "$MAINSAIL_PORT" = "80" ] || [ "$DWC2_PORT" = "80" ] || [ "$OCTOPRINT_PORT" = "80" ]; then PORT_80_BLOCKED="true" - fi - if [ "$PORT_80_BLOCKED" = "true" ]; then - [ "$MAINSAIL_PORT" = "80" ] && echo "${cyan}Mainsail${default} already listens on Port 80!" - [ "$DWC2_PORT" = "80" ] && echo "${cyan}DWC2${default} already listens on Port 80!" - [ "$OCTOPRINT_PORT" = "80" ] && echo "${cyan}OctoPrint${default} already listens on Port 80!" - echo "You need to choose a different Port for Fluidd than the above!" select_fluidd_port fi else @@ -39,16 +33,34 @@ fluidd_port_check(){ } select_fluidd_port(){ - while true; do - read -p "${cyan}Please enter a new Port:${default} " NEW_PORT - if [ "$NEW_PORT" != "$MAINSAIL_PORT" ] && [ "$NEW_PORT" != "$DWC2_PORT" ] && [ "$NEW_PORT" != "$OCTOPRINT_PORT" ]; then - echo "Setting port $NEW_PORT for Mainsail!" - SET_LISTEN_PORT=$NEW_PORT - break - else - echo "That port is already taken! Select a different one!" - fi - done + if [ "$PORT_80_BLOCKED" = "true" ]; then + echo + top_border + echo -e "| ${red}!!!WARNING!!!${default} |" + echo -e "| ${red}You need to choose a different port for Fluidd!${default} |" + echo -e "| ${red}The following web interface is listening at port 80:${default} |" + blank_line + [ "$OCTOPRINT_PORT" = "80" ] && echo "| ● OctoPrint |" + [ "$MAINSAIL_PORT" = "80" ] && echo "| ● Mainsail |" + [ "$DWC2_PORT" = "80" ] && echo "| ● DWC2 |" + blank_line + echo -e "| Make sure you don't choose a port which was already |" + echo -e "| assigned to one of the other web interfaces! |" + blank_line + echo -e "| Be aware: there is ${red}NO${default} sanity check for the following |" + echo -e "| input. So make sure to choose a valid port! |" + bottom_border + while true; do + read -p "${cyan}Please enter a new Port:${default} " NEW_PORT + if [ "$NEW_PORT" != "$MAINSAIL_PORT" ] && [ "$NEW_PORT" != "$DWC2_PORT" ] && [ "$NEW_PORT" != "$OCTOPRINT_PORT" ]; then + echo "Setting port $NEW_PORT for Fluidd!" + SET_LISTEN_PORT=$NEW_PORT + break + else + echo "That port is already taken! Select a different one!" + fi + done + fi } get_fluidd_ver(){ diff --git a/scripts/install_mainsail.sh b/scripts/install_mainsail.sh index ae0a01a..a65ba1e 100755 --- a/scripts/install_mainsail.sh +++ b/scripts/install_mainsail.sh @@ -14,18 +14,12 @@ install_mainsail(){ mainsail_port_check(){ if [ "$MAINSAIL_ENABLED" = "false" ]; then if [ "$SITE_ENABLED" = "true" ]; then - echo "Detected other enabled Interfaces:" - [ "$FLUIDD_ENABLED" = "true" ] && echo "${cyan}● Fluidd - Port:$FLUIDD_PORT${default}" - [ "$DWC2_ENABLED" = "true" ] && echo "${cyan}● DWC2 - Port:$DWC2_PORT${default}" - [ "$OCTOPRINT_ENABLED" = "true" ] && echo "${cyan}● OctoPrint - Port:$OCTOPRINT_PORT${default}" + status_msg "Detected other enabled interfaces:" + [ "$OCTOPRINT_ENABLED" = "true" ] && echo -e " ${cyan}● OctoPrint - Port: $OCTOPRINT_PORT${default}" + [ "$FLUIDD_ENABLED" = "true" ] && echo -e " ${cyan}● Fluidd - Port: $FLUIDD_PORT${default}" + [ "$DWC2_ENABLED" = "true" ] && echo -e " ${cyan}● DWC2 - Port: $DWC2_PORT${default}" if [ "$FLUIDD_PORT" = "80" ] || [ "$DWC2_PORT" = "80" ] || [ "$OCTOPRINT_PORT" = "80" ]; then PORT_80_BLOCKED="true" - fi - if [ "$PORT_80_BLOCKED" = "true" ]; then - [ "$FLUIDD_PORT" = "80" ] && echo "${cyan}Fluidd${default} already listens on Port 80!" - [ "$DWC2_PORT" = "80" ] && echo "${cyan}DWC2${default} already listens on Port 80!" - [ "$OCTOPRINT_PORT" = "80" ] && echo "${cyan}OctoPrint${default} already listens on Port 80!" - echo "You need to choose a different Port for Mainsail than the above!" select_mainsail_port fi else @@ -39,16 +33,34 @@ mainsail_port_check(){ } select_mainsail_port(){ - while true; do - read -p "${cyan}Please enter a new Port:${default} " NEW_PORT - if [ "$NEW_PORT" != "$FLUIDD_PORT" ] && [ "$NEW_PORT" != "$DWC2_PORT" ] && [ "$NEW_PORT" != "$OCTOPRINT_PORT" ]; then - echo "Setting port $NEW_PORT for Mainsail!" - SET_LISTEN_PORT=$NEW_PORT - break - else - echo "That port is already taken! Select a different one!" - fi - done + if [ "$PORT_80_BLOCKED" = "true" ]; then + echo + top_border + echo -e "| ${red}!!!WARNING!!!${default} |" + echo -e "| ${red}You need to choose a different port for Mainsail!${default} |" + echo -e "| ${red}The following web interface is listening at port 80:${default} |" + blank_line + [ "$OCTOPRINT_PORT" = "80" ] && echo "| ● OctoPrint |" + [ "$FLUIDD_PORT" = "80" ] && echo "| ● Fluidd |" + [ "$DWC2_PORT" = "80" ] && echo "| ● DWC2 |" + blank_line + echo -e "| Make sure you don't choose a port which was already |" + echo -e "| assigned to one of the other web interfaces! |" + blank_line + echo -e "| Be aware: there is ${red}NO${default} sanity check for the following |" + echo -e "| input. So make sure to choose a valid port! |" + bottom_border + while true; do + read -p "${cyan}Please enter a new Port:${default} " NEW_PORT + if [ "$NEW_PORT" != "$FLUIDD_PORT" ] && [ "$NEW_PORT" != "$DWC2_PORT" ] && [ "$NEW_PORT" != "$OCTOPRINT_PORT" ]; then + echo "Setting port $NEW_PORT for Mainsail!" + SET_LISTEN_PORT=$NEW_PORT + break + else + echo "That port is already taken! Select a different one!" + fi + done + fi } get_mainsail_ver(){ diff --git a/scripts/install_octoprint.sh b/scripts/install_octoprint.sh index 90e956d..273a129 100755 --- a/scripts/install_octoprint.sh +++ b/scripts/install_octoprint.sh @@ -142,18 +142,12 @@ octoprint_reverse_proxy_dialog(){ octoprint_port_check(){ if [ "$OCTOPRINT_ENABLED" = "false" ]; then if [ "$SITE_ENABLED" = "true" ]; then - echo "Detected other enabled Interfaces:" - [ "$MAINSAIL_ENABLED" = "true" ] && echo "${cyan}● Mainsail - Port:$MAINSAIL_PORT${default}" - [ "$FLUIDD_ENABLED" = "true" ] && echo "${cyan}● Fluidd - Port:$FLUIDD_PORT${default}" - [ "$DWC2_ENABLED" = "true" ] && echo "${cyan}● DWC2 - Port:$DWC2_PORT${default}" + status_msg "Detected other enabled interfaces:" + [ "$MAINSAIL_ENABLED" = "true" ] && echo " ${cyan}● Mainsail - Port:$MAINSAIL_PORT${default}" + [ "$FLUIDD_ENABLED" = "true" ] && echo " ${cyan}● Fluidd - Port:$FLUIDD_PORT${default}" + [ "$DWC2_ENABLED" = "true" ] && echo " ${cyan}● DWC2 - Port:$DWC2_PORT${default}" if [ "$MAINSAIL_PORT" = "80" ] || [ "$DWC2_PORT" = "80" ] || [ "$FLUIDD_PORT" = "80" ]; then PORT_80_BLOCKED="true" - fi - if [ "$PORT_80_BLOCKED" = "true" ]; then - [ "$MAINSAIL_PORT" = "80" ] && echo "${cyan}Mainsail${default} already listens on Port 80!" - [ "$FLUIDD_PORT" = "80" ] && echo "${cyan}Fluidd${default} already listens on Port 80!" - [ "$DWC2_PORT" = "80" ] && echo "${cyan}DWC2${default} already listens on Port 80!" - echo "You need to choose a different Port for OctoPrint than the above!" select_octoprint_port fi else @@ -167,16 +161,34 @@ octoprint_port_check(){ } select_octoprint_port(){ - while true; do - read -p "${cyan}Please enter a new Port:${default} " NEW_PORT - if [ "$NEW_PORT" != "$MAINSAIL_PORT" ] && [ "$NEW_PORT" != "$DWC2_PORT" ] && [ "$NEW_PORT" != "$FLUIDD_PORT" ]; then - echo "Setting port $NEW_PORT for OctoPrint!" - SET_LISTEN_PORT=$NEW_PORT - break - else - echo "That port is already taken! Select a different one!" - fi - done + if [ "$PORT_80_BLOCKED" = "true" ]; then + echo + top_border + echo -e "| ${red}!!!WARNING!!!${default} |" + echo -e "| ${red}You need to choose a different port for OctoPrint!${default} |" + echo -e "| ${red}The following web interface is listening at port 80:${default} |" + blank_line + [ "$MAINSAIL_PORT" = "80" ] && echo "| ● Mainsail |" + [ "$FLUIDD_PORT" = "80" ] && echo "| ● Fluidd |" + [ "$DWC2_PORT" = "80" ] && echo "| ● DWC2 |" + blank_line + echo -e "| Make sure you don't choose a port which was already |" + echo -e "| assigned to one of the other web interfaces! |" + blank_line + echo -e "| Be aware: there is ${red}NO${default} sanity check for the following |" + echo -e "| input. So make sure to choose a valid port! |" + bottom_border + while true; do + read -p "${cyan}Please enter a new Port:${default} " NEW_PORT + if [ "$NEW_PORT" != "$MAINSAIL_PORT" ] && [ "$NEW_PORT" != "$FLUIDD_PORT" ] && [ "$NEW_PORT" != "$DWC2_PORT" ]; then + echo "Setting port $NEW_PORT for OctoPrint!" + SET_LISTEN_PORT=$NEW_PORT + break + else + echo "That port is already taken! Select a different one!" + fi + done + fi } create_config_yaml(){ From 976d9cb5a91471976003d95304e0f13a72383dff Mon Sep 17 00:00:00 2001 From: th33xitus Date: Tue, 6 Oct 2020 20:34:45 +0200 Subject: [PATCH 08/12] fix: add gzip to fluidd nginx config --- resources/fluidd_nginx.cfg | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/resources/fluidd_nginx.cfg b/resources/fluidd_nginx.cfg index ee0cfa2..36adf37 100644 --- a/resources/fluidd_nginx.cfg +++ b/resources/fluidd_nginx.cfg @@ -7,6 +7,16 @@ server { access_log /var/log/nginx/fluidd-access.log; error_log /var/log/nginx/fluidd-error.log; + #disable this section on smaller hardware like a pi zero + gzip on; + gzip_vary on; + gzip_proxied any; + gzip_proxied expired no-cache no-store private auth; + gzip_comp_level 4; + gzip_buffers 16 8k; + gzip_http_version 1.1; + gzip_types text/plain text/css text/xml text/javascript application/x-javascript application/json application/xml; + #web_path from fluidd static files root /home/pi/fluidd; From bc658f38bca6fb21af9a6f474f667a8537fd2b3e Mon Sep 17 00:00:00 2001 From: th33xitus Date: Tue, 6 Oct 2020 21:04:35 +0200 Subject: [PATCH 09/12] fix: UI --- scripts/status.sh | 56 +++++++++++++++++++++++++++++++++++++---- scripts/ui/main_menu.sh | 11 +++++--- 2 files changed, 59 insertions(+), 8 deletions(-) diff --git a/scripts/status.sh b/scripts/status.sh index a772056..875d5d4 100755 --- a/scripts/status.sh +++ b/scripts/status.sh @@ -57,15 +57,38 @@ dwc2_status(){ fi } +moonraker_status(){ + mrcount=0 + moonraker_data=( + $MOONRAKER_SERVICE1 + $MOONRAKER_SERVICE2 + $MOONRAKER_DIR + $MOONRAKER_ENV_DIR + $NGINX_CONFD/upstreams.conf + $NGINX_CONFD/common_vars.conf + ) + #count+1 for each found data-item from array + for mrd in "${moonraker_data[@]}" + do + if [ -e $mrd ]; then + mrcount=$(expr $mrcount + 1) + fi + done + if [ "$mrcount" == "${#moonraker_data[*]}" ]; then + MOONRAKER_STATUS="${green}Installed!${default} " + elif [ "$mrcount" == 0 ]; then + MOONRAKER_STATUS="${red}Not installed!${default} " + else + MOONRAKER_STATUS="${yellow}Incomplete!${default} " + fi +} + mainsail_status(){ mcount=0 mainsail_data=( - $MOONRAKER_SERVICE1 - $MOONRAKER_SERVICE2 $MAINSAIL_DIR - $MOONRAKER_ENV_DIR - /etc/nginx/sites-available/mainsail - /etc/nginx/sites-enabled/mainsail + NGINX_SA/mainsail + NGINX_SE/mainsail ) #count+1 for each found data-item from array for md in "${mainsail_data[@]}" @@ -83,6 +106,29 @@ mainsail_status(){ fi } +fluidd_status(){ + fcount=0 + fluidd_data=( + $FLUIDD_DIR + NGINX_SA/fluidd + NGINX_SE/fluidd + ) + #count+1 for each found data-item from array + for fd in "${fluidd_data[@]}" + do + if [ -e $fd ]; then + fcount=$(expr $fcount + 1) + fi + done + if [ "$fcount" == "${#fluidd_data[*]}" ]; then + FLUIDD_STATUS="${green}Installed!${default} " + elif [ "$fcount" == 0 ]; then + FLUIDD_STATUS="${red}Not installed!${default} " + else + FLUIDD_STATUS="${yellow}Incomplete!${default} " + fi +} + octoprint_status(){ ocount=0 octoprint_data=( diff --git a/scripts/ui/main_menu.sh b/scripts/ui/main_menu.sh index 0844dec..c3280ec 100755 --- a/scripts/ui/main_menu.sh +++ b/scripts/ui/main_menu.sh @@ -6,9 +6,12 @@ main_ui(){ echo -e "| 1) [Install] | Klipper: $KLIPPER_STATUS|" echo -e "| 2) [Update] | Branch: ${cyan}$PRINT_BRANCH${default}|" echo -e "| 3) [Remove] | |" - echo -e "| | DWC2: $DWC2_STATUS|" - echo -e "| 4) [Advanced] | Mainsail: $MAINSAIL_STATUS|" - echo -e "| 5) [Backup] | Octoprint: $OCTOPRINT_STATUS|" + echo -e "| | Moonraker: $MOONRAKER_STATUS|" + echo -e "| 4) [Advanced] | |" + echo -e "| 5) [Backup] | DWC2: $DWC2_STATUS|" + echo -e "| | Fluidd: $FLUIDD_STATUS|" + echo -e "| | Mainsail: $MAINSAIL_STATUS|" + echo -e "| | Octoprint: $OCTOPRINT_STATUS|" echo -e "| | |" quit_footer } @@ -21,7 +24,9 @@ main_menu(){ fi #check install status klipper_status + moonraker_status dwc2_status + fluidd_status mainsail_status octoprint_status print_branch From fb14d9c717e5a950aac2bc07971ea3bfc450bc7d Mon Sep 17 00:00:00 2001 From: th33xitus Date: Tue, 6 Oct 2020 21:14:56 +0200 Subject: [PATCH 10/12] bugfix: fix typos --- scripts/status.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/scripts/status.sh b/scripts/status.sh index 875d5d4..f9e3d60 100755 --- a/scripts/status.sh +++ b/scripts/status.sh @@ -87,8 +87,8 @@ mainsail_status(){ mcount=0 mainsail_data=( $MAINSAIL_DIR - NGINX_SA/mainsail - NGINX_SE/mainsail + $NGINX_SA/mainsail + $NGINX_SE/mainsail ) #count+1 for each found data-item from array for md in "${mainsail_data[@]}" @@ -110,8 +110,8 @@ fluidd_status(){ fcount=0 fluidd_data=( $FLUIDD_DIR - NGINX_SA/fluidd - NGINX_SE/fluidd + $NGINX_SA/fluidd + $NGINX_SE/fluidd ) #count+1 for each found data-item from array for fd in "${fluidd_data[@]}" From 6142c0c18f3a8dcea45d389d81d45c2fcfc40b2f Mon Sep 17 00:00:00 2001 From: th33xitus Date: Tue, 6 Oct 2020 21:58:25 +0200 Subject: [PATCH 11/12] bugfix: typos --- kiauh.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/kiauh.sh b/kiauh.sh index 2b04a44..194f157 100755 --- a/kiauh.sh +++ b/kiauh.sh @@ -17,8 +17,8 @@ KLIPPY_ENV_DIR=${HOME}/klippy-env KLIPPER_SERVICE1=/etc/init.d/klipper KLIPPER_SERVICE2=/etc/default/klipper #nginx -NGINX_SA=/etc/nginx/sites_available -NGINX_SE=/etc/nginx/sites_enabled +NGINX_SA=/etc/nginx/sites-available +NGINX_SE=/etc/nginx/sites-enabled NGINX_CONFD=/etc/nginx/conf.d #moonraker MOONRAKER_DIR=${HOME}/moonraker From ab6fa29bd2515437faea0980598f182c534d8e4b Mon Sep 17 00:00:00 2001 From: th33xitus Date: Tue, 6 Oct 2020 22:29:03 +0200 Subject: [PATCH 12/12] Update README.md --- README.md | 119 ++++++++++++++++------------- resources/screenshots/advanced.png | Bin 13909 -> 0 bytes resources/screenshots/main.png | Bin 13226 -> 26752 bytes resources/screenshots/remove.png | Bin 13103 -> 0 bytes resources/screenshots/update.png | Bin 20901 -> 0 bytes 5 files changed, 68 insertions(+), 51 deletions(-) delete mode 100644 resources/screenshots/advanced.png delete mode 100644 resources/screenshots/remove.png delete mode 100644 resources/screenshots/update.png diff --git a/README.md b/README.md index 9f7b556..16ef0bd 100644 --- a/README.md +++ b/README.md @@ -1,27 +1,24 @@ # KIAUH - Klipper Installation And Update Helper -### ( This script is always work in progress! ) - ![main_menu](https://github.com/th33xitus/kiauh/blob/master/resources/screenshots/main.png) --- -## Disclaimer: Usage of this script happens at your own risk! - -This script is "only" a helping hand for you to get set up in a fast and most comfortable way. - -**This does not mean, it will relieve you of using brain.exe!** +## 📢 Disclaimer: Usage of this script happens at your own risk! +This script is "only" a helping hand for you to get set up in a fast and most comfortable way.\ +**This does not mean, it will relieve you of using brain.exe! 🧠**\ Feel free to give it a try if you want. If you have suggestions or encounter any problems, please report them. --- -## Instructions: +## 🛠️ Instructions: -For downloading this script it is best to have git already installed. -If you haven't, please run `sudo apt-get install git -y` to install git first. You will need it anyways! +For downloading this script it is best to have git already installed.\ +If you haven't, please run `sudo apt-get install git -y` to install git first.\ +You will need it anyways! -After git is installed, use the following commands in the given order to download and execute the script. +After git is installed, use the following commands in the given order to download and execute the script: ```shell cd ~ @@ -31,23 +28,21 @@ chmod +x kiauh.sh scripts/* ./kiauh.sh ``` -## Additional Instructions: +### Additional Instructions: -If you need some more detailed instructions on how to install Klipper and Mainsail with KIAUH, check out this website: - -[Installing Klipper and Mainsail](https://3dp.tumbleweedlabs.com/firmware/klipper-firmware/installing-klipper-and-mainsail-on-your-raspberry-pi) - -Credits for these instructions go to [@tumbleweedlabs](https://github.com/tumbleweedlabs). +If you need some more detailed instructions on how to install Klipper and Mainsail with KIAUH, check out this website:\ +[Installing Klipper and Mainsail](https://3dp.tumbleweedlabs.com/firmware/klipper-firmware/installing-klipper-and-mainsail-on-your-raspberry-pi)\ +Credits for these instructions go to [@tumbleweedlabs](https://github.com/tumbleweedlabs).\ Feel free to check out his work. --- -## Functions and Features: +## 🧰 Functions and Features: ### Core Functions: - **Installing** of the Klipper Firmware to your Raspberry Pi or other Linux Distribution which makes use of init.d. -- **Installing** of several different web interfaces such as Duet Web Control, Mainsail or OctoPrint including their dependencies. +- **Installing** of several different web interfaces such as Duet Web Control, Mainsail, Fluidd or OctoPrint including their dependencies. - **Installing** of the Moonraker API - **Updating** of all the listed installations above excluding OctoPrint. For updating OctoPrint, please use the OctoPrint interface! - **Removing** of all the listed installations above. @@ -65,7 +60,7 @@ For a list of additional features and their descriptions please see: --- -## Notes: +## 📝 Notes: - Important changes to the script will be listed in the [Changelog](https://github.com/th33xitus/kiauh/blob/master/docs/changelog.md) - Tested only on Raspbian Buster Lite @@ -75,41 +70,63 @@ For a list of additional features and their descriptions please see: --- -### For more information or instructions, please check out the appropriate repositories listed below: +## 🛈 Sources & Further Information -Klipper by [KevinOConnor](https://github.com/KevinOConnor) : - -- https://github.com/KevinOConnor/klipper - -Klipper S-Curve fork by [dmbutyugin](https://github.com/dmbutyugin) : - -- https://github.com/dmbutyugin/klipper/tree/scurve-smoothing -- https://github.com/dmbutyugin/klipper/tree/scurve-shaping - -Moonraker by [Arksine](https://github.com/Arksine) : - -- https://github.com/Arksine/moonraker - -Mainsail Webinterface by [meteyou](https://github.com/meteyou) : - -- https://github.com/meteyou/mainsail - -Duet Web Control by [Duet3D](https://github.com/Duet3D) : - -- https://github.com/Duet3D/DuetWebControl - -DWC2-for-Klipper-Socket by [Stephan3](https://github.com/Stephan3) : - -- https://github.com/Stephan3/dwc2-for-klipper-socket - -OctoPrint Webinterface by [OctoPrint](https://github.com/OctoPrint) : - -- https://octoprint.org -- https://github.com/OctoPrint/OctoPrint +For more information or instructions, please check out the appropriate repositories listed below: --- -## Q&A +**⛵Klipper** by [KevinOConnor](https://github.com/KevinOConnor) : + +https://github.com/KevinOConnor/klipper + +--- + +**⛵Klipper S-Curve fork** by [dmbutyugin](https://github.com/dmbutyugin) : + +https://github.com/dmbutyugin/klipper/tree/scurve-smoothing\ +https://github.com/dmbutyugin/klipper/tree/scurve-shaping + +--- + +**🌙Moonraker** by [Arksine](https://github.com/Arksine) : + +https://github.com/Arksine/moonraker + +--- + +**💨Mainsail Webinterface** by [meteyou](https://github.com/meteyou) : + +https://github.com/meteyou/mainsail + +--- + +**🌊Fluidd Webinterface** by [cadriel](https://github.com/cadriel) : + +https://github.com/cadriel/fluidd + +--- + +**🕸️Duet Web Control** by [Duet3D](https://github.com/Duet3D) : + +https://github.com/Duet3D/DuetWebControl + +--- + +**🕸️DWC2-for-Klipper-Socket** by [Stephan3](https://github.com/Stephan3) : + +https://github.com/Stephan3/dwc2-for-klipper-socket + +--- + +**🐙OctoPrint Webinterface** by [OctoPrint](https://github.com/OctoPrint) : + +https://octoprint.org\ +https://github.com/OctoPrint/OctoPrint + +--- + +## ❓ FAQ **_Q: Can i use this script to install multiple instancec of Klipper on the same Pi? (Multisession?)_** diff --git a/resources/screenshots/advanced.png b/resources/screenshots/advanced.png deleted file mode 100644 index a63d7fcf81bd413d578a4643e85f92d0ef2c2e68..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 13909 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 diff --git a/resources/screenshots/main.png b/resources/screenshots/main.png index 993b06c6ca8a2a254eb4b6d9aa10b856d55b4092..ac71eb72f6c3fbfc122d1f3ad46da34131b9125a 100644 GIT binary patch literal 26752 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

s2=X?+XH;G1NF!d z$Dz!my0se2b$?hV=*F!ET~_kQEx*V1Cw3=b_aAGg=DC{Bw@#^n6;WUoOP@FPc@we= zv#&4wKTSJ<@oa6E8iRsSL z(wg?r`KKPZ`58jv`@NEtTc~@CDDTe&fk3_yH)l36a2qn-R}fv_bw{cN+3Pphza}R0 zpn4g~i#Wky5-=BbOq=(rM!j9D%VXY6*KFU^y-~9J^>c(lR(zTO3ZuMfGNjEco=Ml2 zH8PvL=s&E3hMs;SJp{(GX-m(}z*owlrgmtJbJhgCIy&jS0YBTF;T}D*bsF|W!#^=$ zeN?8%b+Yy;a?85che=B{@5!!&^2ZlcB$WFbBHxn7ZkO0NXEW{|ll8^EGHG8eE4W1v z!y{D^`(WO6`mGo`Rx??LtI;Eu&n(~Sp&yT&tI-V~hH8H?Uz#WHYWqfNUKsW=Qc08O zN$HY7AqW9*J@iDs9I15t5bg2!ZgVK(30NGyYL1zAY<_Fs{3yt__C?*%PiVcvhvDbV z5AQO^PlWHTa$og(an}&Jb}G)ZmaQR?DppvawJA5+o{1>z#F)m$;q|E*xmBNPv&T$t zxNr@Lc9rq!Un1zaSC{w=z{~3c-1+GN=e_J+67lv$Vuk4xbLRz0-yCUgfsG(@V(}|& zo{n)8k(e54dUHo(;VibxR)2Bu|NjOE*{GRbjY%=>oz!!odU&tNjJ zrs=^59&NOh`pgcy15NxHsEvjdZ6)ID@s~KHpQh!b`YLsg`?%bdm$^lpJ`N!83ye~8 z#n4v0HPKOjNlxeHofE~aQ(nrn@i>tDKimhAKBDHYz#h|S@+fhgnDh!i5wWzCD*UB) zx4@Thf#UzQ0nOr%cU_GZOB&B5LI#G9gA&Ys&*>%ueMImj7D)p?U2qU-Q6@^OY7XM3 zX4oVWmcP8N`-D@F3v=`|<6N;X<3>@E-P3PpqCiKK*<&R2g;uh4_(kn^`|mmRj`!ao ziv#v{nv`%@`YA1jR~uG3%X$2BDq^|!%Z&P$zjrad8-_`t0msXG_;>WQ)%5=-%gaQ0 z4={puTR%Kqu<+vh4S}-4J%S&W#uv6b^8UlYfT40Lgf+)%M_JF8|90E1{r4MzVn@&GdaBT%+S z-_PxpB9x4e4hjk<&DXHG9CT%NukN^&Skep$I#tWmz?j~?z_@?*zf3mBoeH&z{^xDp z03()^?$*$hRdEN))wD!LWKQ$vr}k#S&>VcEm*fjfb)L;~IphHVeEq*RE%UU0XTl=y zD!LN{uSG$}wwz*}LS~4yuq@-#EF=F=bV|3w{*OlUjE#3OpmC2k{{+ZvcL2W+cBULv zmVyCWJ;eY@ zevmBmTgX@-|9waKwvK7-(t5deTQ}sKroH%zl>g<#b;l9Z%KuE)(yG(qzpjd@2Af0O z^r%w|5@f9Y#p63Si&ZT3qq~G2W|_(;6<*6PQP}kdk#T=#|M&DVO@7f3Dg0=C;=CsC z!_`dH@=koO89qtKKZBeRw~|<@p0?dT(E5(`G7y1~Q?M_69FZaMHwMz)0jzuU^ij&F z_{7HG#L5-(-7X8Uv@6#F$e@4_viTp9dzc*fpj@I!SpZ^QB?;3PPo?rI*LoujM2EjZFuL`g zVn@EMiTdaFqp&7LP@?mrYc$pzPD-a18Ve@%ry0V>x6}o%WvN%ky|BPkFk6~)%H{ZM z+dgm9EA;t^dc$0LHzkxdkL2>|?sy4oFnpL6X{+!k`x&TfhBW7bfP#6))-CLV=M}N= zq&p?ZHIH+29TPB@%3p{kILxy_uvNlaA=RH$LV1NN3e(>E#KHj8;0o!Q9{hHJ^&4i< zsaNG55CeQWZRr`SuFu?E7TH-xZZhqZcv)Hl^JRLss* z6!X2Btv!`H9sV8x)8On*^^JBYSu94a_eV;2Ppsm~Id!>>MtUE{;on*}i&aZafK9#g zEp0VCtb^O6V>EY$&R+PViKitwZ7a2l_V?nyq0AnWbQp8W+JfyBVLR|Z6d)alu5aZh zFCnN$d_gFLEMcPaMgBsn_-2S4*M#c&?EYHyjZ2!%XSbSD8 znUk~F57*`ECjwuMCi80c01&Rg{WaJU0caV-kJXoLwxvw-vl0Wp}o!K75 zdAQJQt3fYEFilIi`I==L0CXC%eP{d4TZ7j_$u#umZNxNy&**E>K6{0XB`tZ&ol+w7IQY}H{ipt6j0#n`GS+!)3lMZRj#qtV>tn) zyT(ZDSKwnbx8a&|_hC5I3tdOmpo8V@izfJYX5emS3b&)#+aF4^)gTwJ3!9qQa1Bf1 z?UqgfkN1m(x8(&$_DYKOXXyws@ur1A1PB#2;RTf^TqUO%sJOl`q~PH~gqZ%F@|5#b z-!0e^M0G2%TTwe%uR%E#MMvqAKV6>^rXRCbB)VI22v>Kp2_YV6K8v3-Lfd^XH1I5iu{1&=7KsnZ)hwn8Eo z3vp^E+?!v_2f{$6M~`_n@Ksma*Vpj82El1Wk)5!gVhYrHyq-yrRDP+AFvh|L7RS3( zeXk^j)Wf|Z2<|lL9`B2L^o2iXG?Ne(1c8UPXiqSTKUf&om)QbjR zFKkBU>nuI~*ZiZFY)=L=Aiyj8!7meZa|HB16fzU~xs~#!kY`rubvsO6i`Q*%AH;pQ zd==?Ec(#fu@*NoEe7>az8}(9v&%RqTh5Uiwj$ClV_Rg9Vk^3GUSWpA{AhO?0{Ff zlqQ4hrr}HEK}S2AZG~M!)^^99$tZhgANicol=9i}kJOnR4kG~2>rqRhDP~*}7$342 z?a8>G^DuH#o7#kxmg@MA-ba6~mnG+%ByWh#Tey@%LK(wowbNgGmSz}t%ypOhx?C*2 z7eVscl;%hHs!~rRhDSU)hdOm+WAfHV?I6&PlqG_3k}Nj;!du|V+fO~Vn=&V==kx`T z??8TYb36CxRnrd+bZOlxOM+igB8nwF?u)Qgj5bs@KUYsZE@<_FKXj!Ex3udG<&Ndj z%z=@%2YHW8XF$}X3rFEIpylZa=X8onripK(oY7^!-xM-Vti0?g2=U}eT&pqRsmrcW zq(sz7KD!5KN)**hWPO9ba$s=;8^|v&SKbUg;w-76G@4wAtkIw=QZRRl=x)FwEb>XnHR=wid5sEq43U46IKCQ)s&FVfYLOEX@0t-p=T z!Dyn6DozqVcpe`;J;U>k9JYMtyhi<_>MeMaup+HupOA^Cj^rbkBf65VgN?(Ixq^UK zn3kUtEAc@#e(&{cJO}E!J#n+KqK5LaokwP}du_RD=0>S#no4!;bIN6yMYL6If+(in zGL4vr@;bx3de->w$lfuv1<``0n#_$ts{VmDe(rj_L1V&YAKZq-W@jH4J07`=Pu!J{ zHu{yUGzR%lPWBAS`pT<1oftJ2(?5}ywyjJ4btusS!ktNPUH03uy75>{-_mYC-z&`A z&BNE8ZsUDSKL3ff&sqMc`v$%f6*l*-*vvvR$EW!>Q+xBqpZcZ>kN5mgZJ94h=hg0l zPK*bB_-GXt&mpoK9=5yDOcMK=`(1N|N+(aOl_HLAA<*06@8@l9kZgjTQXFPBa%@9R zoIBO61(qk?1}G@+E|%DI^%B;+M^%h*)_!y&F%^4hJbPAAIDe0cBW%s`!TqD1_m-G# z$~du%W}(wKy0FjdB|-%;B^IT=5DT+z#OqYbEJpb}FnQrb?I^ST2c=C-l;}0u2`QL! zFLnR$cfyZFkBU>g7nv}cg|yJmDO)acRX&Rk{66kC{je2~ymtp|c&qI5jEBbeZ-<_A zTMT7hbc-b2EN)G<6J*RaIYSW5=O1s)!F(xdu|M3tjV*8Qndy@$F~x|v*zt~j+TH8z zO$ZK29~(R}KIrng>wFDmoE5)#j5pd<(-~y1Wy$*l>pZbG)4Qpe`1wvBpOdkeLQYAr z9wpm7Y2&o5FC&k_Iuu4n$^W-%^QQM?MbyTsC(1#I!1J zR|jm?X^>xvnK;}C{<`}`!=m-B_Iuj>p43t$^t^e^4Q>onZzKCPIQxmE7k=Sc;NUxK zC=Y_xLmm<>=wWN=fhwqMJ{C>>L zioCnOi}%-)*%0%eEsROJ`d>TBcMCV7AXV&X(F@;n?4o9dV8NgV$!y@I1dZfbw?j6+ zIeMj*OgDmvbu*AKt2OflN!MY5nl8r^{+|76063lB=R_fHe64CwI?i;%hTG`erh#fw zzw^8viPslv_k_T>d+ejs8J4$h{<=Qq+2(CyK{FdBY3t2$JL{;{@nAusL8lMAAy6{| zBUe`{P7!yyY!`yrOT~!XQiej^LXiDlIIq1APGr^{mAiasUpkXTe4%y^h%`$9cE+Y; z{6*i!#+|iJwAh8RDzE8V?eI&$erKU)<^D-*XAEf6i<6F3T0e=A8t9rc4OPlkK2&64 z+rE>gOwtXD=f4GZnAgNxZwD;`eG{7f!y#BVc|*-4`8jO^g;vQ?f%MwAgA2 zJ6keW?U=RInzJU2IAsbqw%2w{y8jDZq?9g(E zf;S9f^9osgTJaDN(XOB;(<1rzLy`XHl+PJ)RrlKjw8=n1UcgUn9y+egbd zyP1*_r+Kkvzvdfy5i0Cyjm=0tHhGu>) zjuE8g%*Vs*xJ>8K=I1d{!S6#Q_^cPCc}i}{9=c9dwp$hU_#=9GpyfL|-?EliN!Y)d zY;;sQbqY)+4lq*9!GoKAM8*w{-9DN(O?vG%0be)e8|uS`*<4sfZu(a7j7CIt!!?^+-k#>(wB>rq-=| zi_kL+7iwH#4~|JDrKbz3k<`%2;Uu7hUvZYj8Tp`)@bTK>IEcmJuasLk$I*V zK>d|jv+C_d)(QbmnHXAO_6G!|qnYSppPE_gZK2;Irdc}jwy9xJW#+|5s=TE?^ zW%+a<0k0rB_BFAxM&IGsZB3%Dx-}*?KmDNi$of5Y>Xg$Gq)}PS)9M|tv@F1b*dHBUR;Q@G&Ogb0OTyFRX{ww`; zy5h&DI+2R4?M0DKo!>Di_#_`xf9D$-#k|~FD0H0a9dRt5&puhWj|2+`PaHldL%OBG z)_J9xa9dj!)jOOzuSW|&A6#ef|5@ZQiGaQS3y!GNx=Blt-y;eZh_(}Mcg=E*j+a}P zc^#bIO)K85dCm8_z_q}K(WiQ$NtnT!T5)VT1ijiGUtM}G@ka6zL^U#`lOgi6Mvq3_ zcck{NckuD2OTeC8%v&YaS)qUL*YEY7bk8r6W%m$5BT=@CnA;??W5Q9;yl!y=app_p zMJt{3aNk)D!q+QqCz0}?$N_nF)YiU%o>wPkqSBd=a$aNVF+WPfn$m{7m#o>#Hv0>; z_*NH(0x)ZkmP>FYWygzE2ySl^4C27_=h+Ru2tBknFO*Ssb!#ig>EvUz!-{*RDf6Q~ z6&8PySX#5gZ6)x%0o79Ge$oqW*$kf4NsJ}0)b8w!|M1uG$JqGQNS}7w7Y%kfg%`vE zRH|+JE;A_Z1ZsZSvw`(2Q~zK{=JQI3nbfzWkRZ$e)x>r5XTX9(-Xh?)aBG0eQ%2J! z8zT-2E|Ss`?vwUs)K^1AYQ8g4wZ8sZ)v@z&v`61SVGaxwTS)TeZZMS}ybN(IPVVt? zr!b%U1$Sv)?tS^a{7_5M7F)eZ9BTY27ow9x3}|oL6O&(VN=oUk#L@3+y823uP0hkF zeSH#50FNu&Ff}so{lJhne|?(V?M_=)fImX2+NsH=NF!ij@DO`z<;KXYiN|=v$*c8^ zkm_auV(L*XR;D^*bCa5zEw2p=e$K$_K4I)!ENYT$Mo3C{q(#H5PtDjjpCNEL+&l_} zS?uZMR96MYc;d%-5Ho6po~?uzDj5>Q2Ni?&)(3{=bf#v9H0U^^;ei?FY18Qjw}v8f zlr_(H0c+&a3COqKIj0DJcpt8{dH5ur+zo6ye$hf~F%5|vpg;L4)b!zs@fG#^*T8Yz;|$ZPw69=v*~WAMF-pDjOSIG5 z2V`p%mDn~b7Q2YWbtwwG0FhMB4Lg;k$`mAPTwk?;-oAJex^cuLm$X%$oPl7)g*rg? zz-)3bVT%t|{CvQE3qwG+uzf;%J=A@Y65qIkkR=uZn3eS^(>%Mz^YMroSTZQE_F+!c zr^UTZZKbci`t%~m@L1ooou%8=!uCJmE~In6nk(16e+H~b1iXk^#IAea>67D@jZ2E) z#D6+*aIeKar|-LcYAF^y@WmkEpqxT=>1+?MK~388sc-nOdNZicc($u_FJ068VB*rV z;J-KsEwGwv%PmtaUCi8eomlSv6*a&eDM-&Jw(jW5QMg;2D z)Nl)q^4YJt(0~}*2cpeDI^1c}=68;&-u7C?OD}@B-B#}!laN2C|X48Y&?{Ccz zKfOW#CTKQVn{iJjIDP+obT$U~k(U7*{o9f1%WR^MrS!WM_7OmL+ur!(@73ULy174b zGeYXJ%3km=##z`X-|a;%Y!m|Mucdjn#mU`BeE5-4g+eBs<6g#ZTi)l&IxZq^gYhFg zn(-lTb-EZP%SMGC0B5I(MBZyMCSna^3@aJqth$#$F|@bpyltsaS@ zp3p29RZJdhwmQEoulzid7^FJfy!%U7!cz9zP+rOK;`+_S*HhC4t!RgY4PNBR)uQ{g zs^TF{$fJM(OWBTzPBQRAmJvC;r(fjCc)t+`+bl`*eQlFU)jU_Di+SeD^W(QCMq^sZZ;_O7KYxrQikED76E^b4Y{H)`^_h!4~1pJ6cwi7>@Oi89%rMiq}7c-X3KUOeW z+fu?kr&khQnbMLr!EH4#ZWdMxYUiEpS%ua%+YNp#W}=vRRtaODJ#oFdCYX=O^+gw+ zY#mdte1Ko$?PnA^o)N6hl@@JuD~!y`PJNz)%P;c*p~TS3rkBsoZXoNqu~)4C>;aF!Rc@#wXifBCQnfC(JSml%v8`0QRHCiG>sAz0Rf6;d{YlVR4GJ;0&Q(^7 zO{|8MB~|vtboa87Lrv2PNp4x7u@EFBf?ta-Hor>zHG!r@x`w4??4`Rdi8@)Jf*1Gd zB>4pSqs>9YOvFr={`5_3Cr5>@=aWCyPb#H~ADm`WGe~a4Nj>b&foEaM!hwgPMQA2u zBMx~*2;a(G5<&&ixP&o2Nd_8W-oHT|GpX^Qm}#jrNG_8Vs1P{X0s%$7SBHSn{#!2# zY?wfoMkj@|B>MwTLT2Qe$^yAB?~6mp$f;#!f-$Ffwe81ec2jA%A59No?#c$}witQ9 z>$A0>$sm(TYo3+LS&uMYqvr7-c-3(85+$$lY*hfLx zVhAfVPEl8|TFg{K@>nzZom7^Dw1vlu3i+P64ph;1*i;TP_6Vi~b5rmOg{Mk|!4+QX z$OJ>T<8GJ;*l9ujC~-1NrurP*+-=~2O+p%*or;cPH@~hVz+2Ac3^KQt)dLiW9>zRS zVrrB^)ZeMil}WOY6nv6&pz(w0)@b4ovAqI|}>Ym!MY{NrWYneoYi z4@Aka5|nquTQG15mNY1VB6VoWh%1}+bP^R2xs2_rFHT(do^yhTB zKI6W}ivi2&dOr087gju)Q*{HLFT#CXmm`&(zy@y6A#Ih5Q@AiY+W9!-+ZFKgkM;ae z79iLcR2H>fE&L$#~URr)t%B)-VQ^iVZ-c*U@iaO08EG zmRe}W(C&LC0wlZ+iH~?HP$R2>M_E^)=48f@{Jvw~!aYu-PPtmI{ZS_KZiT(*0)Vlv zg-We8`Y7g>?4FrAMS718?$Eh<@D5D zl`mq7H6_#X9-a4APWJ)zi&M$R#D`3|Lru#gTrU9`9!YK^X}d-a*6^euc|F%_FYsAO zbx_KNOv{2-?y%o$Zmy;VKfp38%+7bU+3MB$~T zg9sU!zhq`K-;N+Y0*YqfXpos~kL*pvt-0j^5SA}dxy5rRA87-=rF&wtYb}l^zb}g( zKdMhcJfA#rZ+Kwg7|#g^IuFm@m?xxpAF_R`s%nfTJb0!Yjt_2gN@Rv4ZD7J5n>yKp zsqBXAs1DivaLG?&3@-155VG0t(gR9SWPYG}# zVd8VC`q7XN1p2Ft_uOCPle>>{$_yGod7YeQ*WS4P`ck|{Z)@W&8jGNUnq1TQ1Io$N zpE_@Ai(GoJ3`oOHH-IOrLUU`vo|6%>&*qDNZn1E$*gFqK9}=qRhV+y29e*=^Lo-hW zE5O#(z72^E5|dL)S3{B_JEF*16~eHu9potvHOofO6}mF4hM@2G2PkDZ^K4?W10P zFJ`2?{eieim@trkUfc|V;3ZIOou$2~v)>Dy$!n5dNTbsEdv20g zRzIx?C00Eg5o6dmtdV%1(a!u%$DHME--+0gF5Q;qj{uLI>?d41N7ICdvGGEIKM>HS z_jgG*NwhsaChp8VLcn%~I2N`lc)s|*o8&%pb&4I?4x%#jqtVR}+_N+&deQY|Yuc)& z#h~(&LS%MX00ro<71hGSOlezbi5-*;|2~-kPW6^3L`_|bavm4Oi4dtH07`!ndI>Rr z*(x_FOfPv#@L*azS>Wb)QQe!eR9z3{q1@8t31Lr41%Y4Aw@DhFHCZIf!xfS`WBP4v zRpc9%Y*0TcfN+xkVC3X;En!6Woy**{jpD)GDvZ?Y_D$*Co#{>9GR@m&h#S#VH$|{w zX>8ymW-%=wR*hpn5`4ZPDt7$aQN-3+Z+hNzk3itWyI*o(9GwK!{9MktaW(Y-2P`%vb>00Co!RE*QVrV z%5^NA^sSC>vgjQDxU4P{oj87=ehK(}nPRuUME&){|9gA8k46wrUDnWf=IUIVD{u}v zdQ`MVMG;}ky*y_xuU z`92V4XRDk3{cX;r%WFSa+q2;iccr%Ko-6id?;cP7G@IAWM!s+!CT0mZOvG1ffV(Dd zC_Up{VR#8YEX$mX1_x!HIrQ2?oI&be(*tG?nXEX0Zk=}fw+Gm71F}mZN6X-qAi7Z( z5@|Rc^nm4C$3!6YRW(&3)TO$)O^32Z7plh9DgnoGO5p-<+O=|QRmRcQ_~4H KL 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 Km+Ibm^gjS+2rT;m diff --git a/resources/screenshots/remove.png b/resources/screenshots/remove.png deleted file mode 100644 index cf1f665b5372c926ef6d97a3c8d91a91d1e552c8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 13103 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 diff --git a/resources/screenshots/update.png b/resources/screenshots/update.png deleted file mode 100644 index 7ef37678f7a672f014073c375b2037c07779a122..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 20901 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