mirror of
https://github.com/dw-0/kiauh.git
synced 2025-12-23 15:53:36 +05:00
Compare commits
142 Commits
v3
...
3d5e83d5ab
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3d5e83d5ab | ||
|
|
edd5f5c6fd | ||
|
|
8ff0b9d81d | ||
|
|
22e8e314db | ||
|
|
12bd8eb799 | ||
|
|
4915896099 | ||
|
|
cd38970bbd | ||
|
|
b8640f45a6 | ||
|
|
5fb4444f03 | ||
|
|
926ba1acb4 | ||
|
|
c2e7ee98df | ||
|
|
3865266da1 | ||
|
|
b83f642a13 | ||
|
|
30b4414469 | ||
|
|
1178d3c730 | ||
|
|
59d8867c8c | ||
|
|
80a953a587 | ||
|
|
a80f0bb0e8 | ||
|
|
78cefddb2e | ||
|
|
b20613819e | ||
|
|
1836beab42 | ||
|
|
545397f598 | ||
|
|
f709cf84e7 | ||
|
|
f62c10dc8b | ||
|
|
e121ba8a62 | ||
|
|
9a1a66aa64 | ||
|
|
420b193f4b | ||
|
|
de20f0c412 | ||
|
|
57f34b07c6 | ||
|
|
e35e44a76a | ||
|
|
bfb10c742b | ||
|
|
458c89a78a | ||
|
|
6128e35d45 | ||
|
|
279d000bb0 | ||
|
|
a4a3d5eecb | ||
|
|
1392ca9f82 | ||
|
|
47121f6875 | ||
|
|
d0d2404132 | ||
|
|
6ed5395f17 | ||
|
|
be805c169b | ||
|
|
eaf12db27e | ||
|
|
fe8767113b | ||
|
|
2148d95cf4 | ||
|
|
682be48e8d | ||
|
|
68369753fd | ||
|
|
44ed3b6ddf | ||
|
|
e12e578098 | ||
|
|
515a42f098 | ||
|
|
f9ecad0eca | ||
|
|
fb09acf660 | ||
|
|
093da73dd1 | ||
|
|
c9e8c4807e | ||
|
|
09e874214b | ||
|
|
623bd7553b | ||
|
|
1e0c74b549 | ||
|
|
358c666da9 | ||
|
|
84a530be7d | ||
|
|
bfff3019cb | ||
|
|
2a100c2934 | ||
|
|
ce0daa52ae | ||
|
|
f45da66e9e | ||
|
|
2822499344 | ||
|
|
c777ba3e6b | ||
|
|
9f410450d7 | ||
|
|
0497d49066 | ||
|
|
229da227b0 | ||
|
|
65854c8da6 | ||
|
|
5985646633 | ||
|
|
979c39dc02 | ||
|
|
197058bd00 | ||
|
|
d3b5122ebb | ||
|
|
8ce4daf403 | ||
|
|
b0a65fe14e | ||
|
|
98866caefa | ||
|
|
345b7b66a3 | ||
|
|
8eb2924832 | ||
|
|
5d7debd65e | ||
|
|
7df3dd489f | ||
|
|
0cd058320f | ||
|
|
bcbb185bd7 | ||
|
|
477f3ca72c | ||
|
|
c19acb1694 | ||
|
|
8228943850 | ||
|
|
5b890fb0fb | ||
|
|
7989cec8d4 | ||
|
|
858301aa9a | ||
|
|
ae9e79c579 | ||
|
|
1215446a6c | ||
|
|
8526acf8b6 | ||
|
|
cc27aaec7c | ||
|
|
1e9493461c | ||
|
|
31616ebad5 | ||
|
|
faf56ed1b1 | ||
|
|
d6837af2a2 | ||
|
|
afe6f7499a | ||
|
|
e3ed223b5c | ||
|
|
fd27db28d4 | ||
|
|
68a02ad3f5 | ||
|
|
99b7672dc9 | ||
|
|
bb3ec79756 | ||
|
|
ce595abd60 | ||
|
|
c79dc280e3 | ||
|
|
7aa186e8b9 | ||
|
|
8493269c6f | ||
|
|
150ef0142f | ||
|
|
f70faa52cc | ||
|
|
e796f74640 | ||
|
|
2c9f5bed60 | ||
|
|
e9c23ca93e | ||
|
|
67afa26ed7 | ||
|
|
54be7e4e21 | ||
|
|
811c071b74 | ||
|
|
6116fc92cf | ||
|
|
5524a40f04 | ||
|
|
cb3661b8b5 | ||
|
|
2cec90b29c | ||
|
|
d2c009df9a | ||
|
|
046178f801 | ||
|
|
442980dbd0 | ||
|
|
798e56f4dc | ||
|
|
9d90daec7f | ||
|
|
f25726cfed | ||
|
|
f46b099b74 | ||
|
|
03be46f012 | ||
|
|
c11e628c55 | ||
|
|
4c8d43e365 | ||
|
|
9d7144b493 | ||
|
|
6df388f42b | ||
|
|
1d7fb010af | ||
|
|
d4207d710c | ||
|
|
6cb8d70b63 | ||
|
|
ae011963da | ||
|
|
491d6f40bb | ||
|
|
8bbe2f79ea | ||
|
|
0bdf61a714 | ||
|
|
b07a83c8ad | ||
|
|
39e22acbed | ||
|
|
8ba46fa4ac | ||
|
|
d6b95c9d10 | ||
|
|
f3a769e03e | ||
|
|
646e5acd3a | ||
|
|
fcf059df73 |
2
.github/FUNDING.yml
vendored
2
.github/FUNDING.yml
vendored
@@ -3,7 +3,7 @@
|
||||
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
||||
patreon: # Replace with a single Patreon username
|
||||
open_collective: # Replace with a single Open Collective username
|
||||
ko_fi: th33xitus
|
||||
ko_fi: dw__0
|
||||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
||||
liberapay: # Replace with a single Liberapay username
|
||||
|
||||
7
.gitignore
vendored
7
.gitignore
vendored
@@ -1,2 +1,7 @@
|
||||
.vscode
|
||||
.idea
|
||||
.shellcheckrc
|
||||
.pytest_cache
|
||||
.kiauh-env
|
||||
*.code-workspace
|
||||
*.iml
|
||||
kiauh.cfg
|
||||
|
||||
15
.shellcheckrc
Normal file
15
.shellcheckrc
Normal file
@@ -0,0 +1,15 @@
|
||||
source=scripts
|
||||
|
||||
enable=avoid-nullary-conditions
|
||||
enable=deprecate-which
|
||||
enable=quote-safe-variables
|
||||
enable=require-variable-braces
|
||||
enable=require-double-brackets
|
||||
|
||||
# SC2162: `read` without `-r` will mangle backslashes.
|
||||
# https://github.com/koalaman/shellcheck/wiki/SC2162
|
||||
disable=SC2162
|
||||
|
||||
# SC2164: Use `cd ... || exit` in case `cd` fails
|
||||
# https://github.com/koalaman/shellcheck/wiki/SC2164
|
||||
disable=SC2164
|
||||
208
README.md
208
README.md
@@ -1,72 +1,190 @@
|
||||

|
||||
# Klipper Installation And Update Helper
|
||||
     
|
||||
<p align="center">
|
||||
<a>
|
||||
<img src="https://raw.githubusercontent.com/dw-0/kiauh/master/resources/screenshots/kiauh.png" alt="KIAUH logo" height="181">
|
||||
<h1 align="center">Klipper Installation And Update Helper</h1>
|
||||
</a>
|
||||
</p>
|
||||
|
||||
### **📋 Please see the [Changelog](docs/changelog.md) for possible important information !**
|
||||
<p align="center">
|
||||
A handy installation script that makes installing Klipper (and more) a breeze!
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a><img src="https://img.shields.io/github/license/dw-0/kiauh"></a>
|
||||
<a><img src="https://img.shields.io/github/stars/dw-0/kiauh"></a>
|
||||
<a><img src="https://img.shields.io/github/forks/dw-0/kiauh"></a>
|
||||
<a><img src="https://img.shields.io/github/languages/top/dw-0/kiauh?logo=gnubash&logoColor=white"></a>
|
||||
<a><img src="https://img.shields.io/github/v/tag/dw-0/kiauh"></a>
|
||||
<br />
|
||||
<a><img src="https://img.shields.io/github/last-commit/dw-0/kiauh"></a>
|
||||
<a><img src="https://img.shields.io/github/contributors/dw-0/kiauh"></a>
|
||||
</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<h2 align="center">
|
||||
📄️ Instructions 📄
|
||||
</h2>
|
||||
|
||||
### 📋 Prerequisites
|
||||
KIAUH is a script that assists you in installing Klipper on a Linux operating system that has
|
||||
already been flashed to your Raspberry Pi's (or other SBC's) SD card. As a result, you must ensure
|
||||
that you have a functional Linux system on hand. `Raspberry Pi OS Lite (32bit)` is a recommended Linux image
|
||||
if you are using a Raspberry Pi. The [official Raspberry Pi Imager](https://www.raspberrypi.com/software/)
|
||||
is the simplest way to flash an image like this to an SD card.
|
||||
|
||||
* Once you have downloaded, installed and launched the Raspberry Pi Imager,
|
||||
select `Choose OS -> Raspberry Pi OS (other)`: \
|
||||
<p align="center">
|
||||
<img src="https://raw.githubusercontent.com/dw-0/kiauh/master/resources/screenshots/rpi_imager1.png" alt="KIAUH logo" height="350">
|
||||
</p>
|
||||
|
||||
* Then select `Raspberry Pi OS Lite (32bit)`:
|
||||
<p align="center">
|
||||
<img src="https://raw.githubusercontent.com/dw-0/kiauh/master/resources/screenshots/rpi_imager2.png" alt="KIAUH logo" height="350">
|
||||
</p>
|
||||
|
||||
* Back in the Raspberry Pi Imager's main menu, select the corresponding SD card to which
|
||||
you want to flash the image.
|
||||
|
||||
* Make sure to go into the Advanced Option (the cog icon in the lower left corner of the main menu)
|
||||
and enable SSH and configure Wi-Fi.
|
||||
|
||||
* If you need more help for using the Raspberry Pi Imager, please visit the [official documentation](https://www.raspberrypi.com/documentation/computers/getting-started.html).
|
||||
|
||||
These steps **only** apply if you are actually using a Raspberry Pi. In case you want
|
||||
to use a different SBC (like an Orange Pi or any other Pi derivates), please look up on how to get an appropriate Linux image flashed
|
||||
to the SD card before proceeding further (usually done with Balena Etcher in those cases). Also make sure that KIAUH will be able to run
|
||||
and operate on the Linux Distribution you are going to flash. You likely will have the most success with
|
||||
distributions based on Debian 11 Bullseye. Read the notes further down below in this document.
|
||||
|
||||
### 💾 Download and use KIAUH
|
||||
**📢 Disclaimer: Usage of this script happens at your own risk!**
|
||||
|
||||
* **Step 1:** \
|
||||
To download this script, it is necessary to have git installed. If you don't have git already installed, or if you are unsure, run the following command:
|
||||
```shell
|
||||
sudo apt-get update && sudo apt-get install git -y
|
||||
```
|
||||
|
||||
## **🛠️ Instructions:**
|
||||
|
||||
For downloading this script it is necessary to have git installed.\
|
||||
If you haven't, please run `sudo apt-get install git -y` to install git first.\
|
||||
After git is installed, use the following commands in the given order to download and execute the script:
|
||||
* **Step 2:** \
|
||||
Once git is installed, use the following command to download KIAUH into your home-directory:
|
||||
|
||||
```shell
|
||||
cd ~
|
||||
cd ~ && git clone https://github.com/dw-0/kiauh.git
|
||||
```
|
||||
|
||||
git clone https://github.com/th33xitus/kiauh.git
|
||||
* **Step 3:** \
|
||||
Finally, start KIAUH by running the next command:
|
||||
|
||||
```shell
|
||||
./kiauh/kiauh.sh
|
||||
```
|
||||
|
||||
* **Step 4:** \
|
||||
You should now find yourself in the main menu of KIAUH. You will see several actions to choose from depending
|
||||
on what you want to do. To choose an action, simply type the corresponding number into the "Perform action"
|
||||
prompt and confirm by hitting ENTER.
|
||||
|
||||
## **🧰 Functions and Features:**
|
||||
<hr>
|
||||
|
||||
### **Core Functions:**
|
||||
<h2 align="center">❗ Notes ❗</h2>
|
||||
|
||||
- **Installing** Klipper to your Raspberry Pi or other Debian based Linux Distribution.
|
||||
- **Installing** of the Moonraker API (needed for Mainsail, Fluidd and KlipperScreen)
|
||||
- **Installing** several web interfaces such as Mainsail, Fluidd, Duet Web Control or OctoPrint including their dependencies.
|
||||
- **Installing** of KlipperScreen (OctoScreen but for Klipper!)
|
||||
- **Updating** of all the listed installations above excluding OctoPrint. For updating OctoPrint, please use the OctoPrint interface!
|
||||
- **Removing** of all the listed installations above.
|
||||
- **Backup** of all the listed installations above.
|
||||
### **📋 Please see the [Changelog](docs/changelog.md) for possible important changes!**
|
||||
|
||||
### **Also possible:**
|
||||
- Mainly tested on Raspberry Pi OS Lite (Debian 10 Buster / Debian 11 Bullseye)
|
||||
- Other Debian based distributions (like Ubuntu 20 to 22) likely work too
|
||||
- Reported to work on Armbian as well but not tested in detail
|
||||
- During the use of this script you will be asked for your sudo password. There are several functions involved which need sudo privileges.
|
||||
|
||||
- Build the Klipper Firmware
|
||||
- Flash the MCU
|
||||
- Read ID of the currently connected MCU
|
||||
- and more ...
|
||||
<hr>
|
||||
|
||||
### **For a list of additional features please see: [Feature List](docs/features.md)**
|
||||
<h2 align="center">🌐 Sources & Further Information</h2>
|
||||
|
||||
## **❗ Notes:**
|
||||
<table>
|
||||
<tr>
|
||||
<th><h3><a href="https://github.com/Klipper3d/klipper">Klipper</a></h3></th>
|
||||
<th><h3><a href="https://github.com/Arksine/moonraker">Moonraker</a></h3></th>
|
||||
<th><h3><a href="https://github.com/mainsail-crew/mainsail">Mainsail</a></h3></th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><img src="https://raw.githubusercontent.com/Klipper3d/klipper/master/docs/img/klipper-logo.png" alt="Klipper Logo" height="64"></th>
|
||||
<th><img src="https://avatars.githubusercontent.com/u/9563098?v=4" alt="Arksine avatar" height="64"></th>
|
||||
<th><img src="https://raw.githubusercontent.com/mainsail-crew/docs/master/assets/img/logo.png" alt="Mainsail Logo" height="64"></th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>by <a href="https://github.com/KevinOConnor">KevinOConnor</a></th>
|
||||
<th>by <a href="https://github.com/Arksine">Arksine</a></th>
|
||||
<th>by <a href="https://github.com/mainsail-crew">mainsail-crew</a></th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><h3><a href="https://github.com/fluidd-core/fluidd">Fluidd</a></h3></th>
|
||||
<th><h3><a href="https://github.com/jordanruthe/KlipperScreen">KlipperScreen</a></h3></th>
|
||||
<th><h3><a href="https://github.com/OctoPrint/OctoPrint">OctoPrint</a></h3></th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><img src="https://raw.githubusercontent.com/fluidd-core/fluidd/master/docs/assets/images/logo.svg" alt="Fluidd Logo" height="64"></th>
|
||||
<th><img src="https://avatars.githubusercontent.com/u/31575189?v=4" alt="jordanruthe avatar" height="64"></th>
|
||||
<th><img src="https://camo.githubusercontent.com/627be7fc67195b626b298af9b9677d7c58e698c67305e54324cffbe06130d4a4/68747470733a2f2f6f63746f7072696e742e6f72672f6173736574732f696d672f6c6f676f2e706e67" alt="OctoPrint Logo" height="64"></th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>by <a href="https://github.com/fluidd-core">fluidd-core</a></th>
|
||||
<th>by <a href="https://github.com/jordanruthe">jordanruthe</a></th>
|
||||
<th>by <a href="https://github.com/OctoPrint">OctoPrint</a></th>
|
||||
</tr>
|
||||
|
||||
- Tested **only** on Raspberry Pi OS Lite (Debian 10 Buster)
|
||||
- Other Debian based distributions can work
|
||||
- Reported to work on Armbian too
|
||||
- During the use of this script you might be asked for your sudo password. There are several functions involved which need sudo privileges.
|
||||
<tr>
|
||||
<th><h3><a href="https://github.com/nlef/moonraker-telegram-bot">Moonraker-Telegram-Bot</a></h3></th>
|
||||
<th><h3><a href="https://github.com/Kragrathea/pgcode">PrettyGCode for Klipper</a></h3></th>
|
||||
<th><h3><a href="https://github.com/TheSpaghettiDetective/moonraker-obico">Obico for Klipper</a></h3></th>
|
||||
</tr>
|
||||
|
||||
## **🌐 Sources & Further Information**
|
||||
<tr>
|
||||
<th><img src="https://avatars.githubusercontent.com/u/52351624?v=4" alt="nlef avatar" height="64"></th>
|
||||
<th><img src="https://avatars.githubusercontent.com/u/5917231?v=4" alt="Kragrathea avatar" height="64"></th>
|
||||
<th><img src="https://avatars.githubusercontent.com/u/46323662?s=200&v=4" alt="Obico logo" height="64"></th>
|
||||
</tr>
|
||||
|
||||
For more information or instructions to the various components KIAUH can install, please check out the corresponding repositories listed below:
|
||||
<tr>
|
||||
<th>by <a href="https://github.com/nlef">nlef</a></th>
|
||||
<th>by <a href="https://github.com/Kragrathea">Kragrathea</a></th>
|
||||
<th>by <a href="https://github.com/TheSpaghettiDetective">Obico</a></th>
|
||||
</tr>
|
||||
|
||||
* ⛵[Klipper](https://github.com/Klipper3d/klipper) by [KevinOConnor](https://github.com/KevinOConnor)
|
||||
* 🌙[Moonraker](https://github.com/Arksine/moonraker) by [Arksine](https://github.com/Arksine)
|
||||
* 💨[Mainsail](https://github.com/mainsail-crew/mainsail) by [mainsail-crew](https://github.com/mainsail-crew)
|
||||
* 🌊[Fluidd](https://github.com/fluidd-core/fluidd) by [fluidd-core](https://github.com/fluidd-core)
|
||||
* 🕸️[Duet Web Control](https://github.com/Duet3D/DuetWebControl) by [Duet3D](https://github.com/Duet3D)
|
||||
* 🕸️[DWC2-for-Klipper-Socket](https://github.com/Stephan3/dwc2-for-klipper-socket) by [Stephan3](https://github.com/Stephan3)
|
||||
* 🖥️[KlipperScreen](https://github.com/jordanruthe/KlipperScreen) by [jordanruthe](https://github.com/jordanruthe)
|
||||
* 🐙[OctoPrint](https://github.com/OctoPrint/OctoPrint) by [OctoPrint](https://github.com/OctoPrint)
|
||||
* 🔬[PrettyGCode](https://github.com/Kragrathea/pgcode) by [Kragrathea](https://github.com/Kragrathea)
|
||||
* 🤖[Moonraker-Telegram-Bot](https://github.com/nlef/moonraker-telegram-bot) by [nlef](https://github.com/nlef)
|
||||
<tr>
|
||||
<th><h3><a href="https://github.com/Clon1998/mobileraker_companion">Mobileraker's Companion</a></h3></th>
|
||||
<th><h3><a href="https://octoeverywhere.com/?source=kiauh_readme">OctoEverywhere For Klipper</a></h3></th>
|
||||
<th><h3></h3></th>
|
||||
</tr>
|
||||
|
||||
## **Credits**
|
||||
<tr>
|
||||
<th><a href="https://github.com/Clon1998/mobileraker_companion"><img src="https://raw.githubusercontent.com/Clon1998/mobileraker/master/assets/icon/mr_appicon.png" alt="OctoEverywhere Logo" height="64"></th>
|
||||
<th><a href="https://octoeverywhere.com/?source=kiauh_readme"><img src="https://octoeverywhere.com/img/logo.svg" alt="OctoEverywhere Logo" height="64"></a></th>
|
||||
<th></th>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<th>by <a href="https://github.com/Clon1998">Patrick Schmidt</a></th>
|
||||
<th>by <a href="https://github.com/QuinnDamerell">Quinn Damerell</a></th>
|
||||
<th></th>
|
||||
</tr>
|
||||
|
||||
|
||||
</table>
|
||||
|
||||
<hr>
|
||||
|
||||
<h2 align="center">✨ Credits ✨</h2>
|
||||
|
||||
* A big thank you to [lixxbox](https://github.com/lixxbox) for that awesome KIAUH-Logo!
|
||||
* Also a big thank you to everyone who supported my work with a [Ko-fi](https://ko-fi.com/th33xitus) !
|
||||
* Also, a big thank you to everyone who supported my work with a [Ko-fi](https://ko-fi.com/dw__0) !
|
||||
* Last but not least: Thank you to all contributors and members of the Klipper Community who like and share this project!
|
||||
|
||||
<hr>
|
||||
|
||||
<h4 align="center">A special thank you to JetBrains for sponsoring this project with their incredible software!</h4>
|
||||
<p align="center">
|
||||
<a href="https://www.jetbrains.com/community/opensource/#support" target="_blank">
|
||||
<img src="https://resources.jetbrains.com/storage/products/company/brand/logos/jb_beam.png" alt="JetBrains Logo (Main) logo." height="128">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
@@ -2,6 +2,105 @@
|
||||
|
||||
This document covers possible important changes to KIAUH.
|
||||
|
||||
### 2023-06-17
|
||||
KIAUH has now added support for installing Mobileraker's companion!
|
||||
Mobileraker is a free and Open Source Android and iOS App for Klipper, utilizing the Moonraker API, allowing you
|
||||
to control your printer. Thank you to [Clon1998](https://github.com/Clon1998) for adding this feature!
|
||||
|
||||
### 2023-02-03
|
||||
The installer for MJPG-Streamer got replaced by crowsnest. It is an improved webcam service, utilizing ustreamer.
|
||||
Please have a look here for additional info about crowsnest and how to configure it: https://github.com/mainsail-crew/crowsnest \
|
||||
It's unsure if the previous MJPG-Streamer installer will be updated and make its way back into KIAUH.
|
||||
A big thanks to [KwadFan](https://github.com/KwadFan) for writing the crowsnest implementation.
|
||||
|
||||
### 2022-10-31
|
||||
Some functions got updated, though not all of them.
|
||||
|
||||
The following functions are still currently unavailable:
|
||||
- Installation of: MJPG-Streamer
|
||||
- All backup functions and the Log-Upload
|
||||
|
||||
### 2022-10-20
|
||||
KIAUH has now reached major version 5 !
|
||||
|
||||
Recently Moonraker introduced some changes which makes it necessary to change the folder structure of printer setups.
|
||||
If you are interested in the details, check out this PR: https://github.com/Arksine/moonraker/pull/491 \
|
||||
Although Moonraker has some mechanics available to migrate existing setups to the new file structure with the use of symlinks, fresh and clean installs
|
||||
should be considered.
|
||||
|
||||
The version jump of KIAUH to v5 is a breaking change due to those major changes! That means v4 and v5 are not compatible with each other!
|
||||
This is also the reason why you will currently be greeted by a yellow notification in the main menu of KIAUH leading to this changelog.
|
||||
I decided to disable a few functions of the script and focus on releasing the required changes to the core components of this script.
|
||||
I will work on updating the other parts of the script piece by piece during the next days/weeks.
|
||||
So I am already sorry in advance if one of your desired components you wanted to install or use temporarily cannot be installed or used right now.
|
||||
|
||||
The following functions are currently unavailable:
|
||||
- Installation of: KlipperScreen, Obico, Octoprint, MJPG-Streamer, Telegram Bot and PrettyGCode
|
||||
- All backup functions and the Log-Upload
|
||||
|
||||
**So what is working?**\
|
||||
Installation of Klipper, Moonraker, Mainsail and Fluidd. Both, single and multi-instance setups work!\
|
||||
As already said, the rest will follow in the near future. Updating and removal of already installed components should continue to work.
|
||||
|
||||
**What was removed?**\
|
||||
The option to change Klippers configuration directory got removed. From now on it will not be possible anymore to change
|
||||
the configuration directory from within KIAUH and the new filestructure is enforced.
|
||||
|
||||
**What if I don't have an existing Klipper/Moonraker install right now?**\
|
||||
Nothing important to think about, install Klipper and Moonraker. KIAUH will install both of them with the new filestructure.
|
||||
|
||||
**What if I have an existing Klipper/Moonraker install?**\
|
||||
First of all: Backups! Please copy all of your config files and the Moonraker database (it is a hidden folder, usually `~/.moonraker_database`) to a safe location.
|
||||
After that, uninstall Klipper and Moonraker with KIAUH. You can then proceed and re-install both of them with KIAUH again. It is important that you are on KIAUH v5 for that!
|
||||
Once everything is installed again, you need to manually copy your configuration files from the old `~/klipper_config` folder to the new `~/printer_data/config` folder.
|
||||
Previous, by Moonraker created symlinks to folder of the old filestructure will not work anymore, you need to move the files to their new location now!
|
||||
Do the same with the two files inside of `~/.moonraker_database`. Move/copy them into `~/printer_data/database`. If `~/printer_data/database` is already populated with a `data.mdb` and `lock.mdb`
|
||||
delete them or simply overwrite them. Nothing should be lost as those should be empty database files. Anyway, you made backups, right?
|
||||
You can now proceed and restart Moonraker. Either from within Mainsail or Fluidd, or use SSH and execute `sudo systemctl restart moonraker`.
|
||||
If everything went smooth, you should be good to go again. If you see some Moonraker warnings about deprecated options in the `moonraker.conf`, go ahead and resolve them.
|
||||
I will not cover them in detail here. A good source is the Moonraker documentation: https://moonraker.readthedocs.io/en/latest/configuration/
|
||||
|
||||
**What if I have an existing Klipper/Moonraker multi-instance install?**\
|
||||
Pretty much the same steps that are required for single instance installs apply to multi-instance setups. So please go ahead and read the previous paragraph if you didn't already.
|
||||
Make backups of everything first. Then remove and install the desired amount of Klipper and Moonraker instances again.
|
||||
Now you need to move all config and database files to their new locations.\
|
||||
Example with an instance called `printer_1`:\
|
||||
The config files go from `~/klipper_config/printer_1` to `~/printer_1_data/config`.
|
||||
The database files go from `~/.moonraker_database_1` to `~/printer_1_data/database`.
|
||||
Now restart all Moonraker services. You can restart all of them at once if you launch KIAUH, and in the main menu type `restart moonraker` and hit Enter.
|
||||
|
||||
I hope I have covered the most important things. In case you need further support, the official Klipper Discord is a good place to ask for help.
|
||||
|
||||
### 2022-08-15
|
||||
Support for "Obico for Klipper" was added! Huge thanks to [kennethjiang](https://github.com/kennethjiang) for helping me with the implementation!
|
||||
|
||||
### 2022-05-29
|
||||
KIAUH has now reached major version 4 !
|
||||
* feat: Klipper can be installed under Python3 (still considered as experimental)
|
||||
* feat: Klipper can be installed from custom repositories / inofficial forks
|
||||
* feat: Custom instance name for multi instance installations of Klipper
|
||||
* Any other multi instance will share the same name given to the corresponding Klipper instance
|
||||
* E.g. klipper-voron2 -> moonraker-voron2 -> moonraker-telegram-bot-voron2
|
||||
* feat: Option to allow installation of / updating to unstable Mainsail and Fluidd versions
|
||||
* by default only stable versions get installed/updated
|
||||
* feat: Multi-Instance OctoPrint installations now each have their own virtual python environment
|
||||
* allows independent installation of plugins for each instance
|
||||
* feat: Implementing the use of shellcheck during development
|
||||
* feat: Implementing a simple logging mechanic
|
||||
* feat: Log-upload function now also allows uploading other logfiles (kiauh.log, webcamd.log etc.)
|
||||
* feat: added several new help dialogs which try to explain various functions
|
||||
* fix: During Klipper installation, checks for group membership of `tty` and `dialout` are made
|
||||
* refactor: rework of the settings menu for better control the new KIAUH features
|
||||
* refactor: Support for DWC and DWC-for-Klipper has been removed
|
||||
* refactor: The backup before update settings were moved to the KIAUH settings menu
|
||||
* refactor: Switch branch function has been removed (was replaced by the custom Klipper repo feature)
|
||||
* refactor: The update manager sections for Mainsail, Fluidd and KlipperScreen were removed from the moonraker.conf template
|
||||
* They will now be individually added during installation of the corresponding interface
|
||||
* refactor: The rollback function was reworked and now also allows rollbacks of Moonraker
|
||||
* It now takes numerical inputs and reverts the corresponding repository by the given amount instead
|
||||
* KIAUH does not save previous states to its config anymore like it did with the previous approach
|
||||
|
||||
|
||||
### 2022-01-29
|
||||
* Starting from the 28th of January, Moonraker can make use of PackageKit and PolicyKit.\
|
||||
More details on that can be found [here](
|
||||
@@ -114,9 +213,9 @@ Each service gets its corresponding instance added to the service filename.
|
||||
--> moonraker-2.service
|
||||
--> moonraker-n.service
|
||||
```
|
||||
* The same service file rules from above apply to DWC and OctoPrint even though only Klipper and Moonraker are shown in this example.
|
||||
* The same service file rules from above apply to OctoPrint even though only Klipper and Moonraker are shown in this example.
|
||||
|
||||
* You can start, stop and restart all Klipper, Moonraker, DWC and OctoPrint instances from the KIAUH main menu. For doing this, just type "stop klipper", "start moonraker", "restart octoprint" and so on.
|
||||
* You can start, stop and restart all Klipper, Moonraker and OctoPrint instances from the KIAUH main menu. For doing this, just type "stop klipper", "start moonraker", "restart octoprint" and so on.
|
||||
|
||||
* KIAUH v3.0 relocated its ini-file. It is now a hidden file in the users home-directory calles `.kiauh.ini`. This has the benefit of keeping all values in that file between possible re-installations of KIAUH. Otherwise that file would be lost.
|
||||
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
# Feature List:
|
||||
|
||||
- **Automatic dependency check:**\
|
||||
If packages are missing but needed for the asked task, the script will automatically install them
|
||||
- **Switch between different Klipper Forks:**\
|
||||
[origin/master](https://github.com/KevinOConnor/klipper/tree/master) or [scurve-shaping](https://github.com/dmbutyugin/klipper/tree/scurve-shaping) or [scurve-smoothing](https://github.com/dmbutyugin/klipper/tree/scurve-smoothing)\
|
||||
The update function of the script will always update the currently selected/active fork!
|
||||
- **Toggle auto-create backups before updating:**\
|
||||
When enabled, a backup of the installation you want to update is made prior updating
|
||||
- **Rollback:**\
|
||||
When updating Klipper, KIAUH saves the current commit hash to a local ini-file. In case of an unsuccesfull update you can use this function to quickly revert back to the commit with the hash you updated from.
|
||||
- **Preconfigure OctoPrint:**\
|
||||
When installing OctoPrint, a config is created which preconfigures your installation to be used with Klipper.\
|
||||
That means:
|
||||
- adding the restart/shutdown commands for OctoPrint
|
||||
- adding the serial port `/tmp/printer`
|
||||
- set the behavior to "Cancel any ongoing prints but stay connected to the printer"
|
||||
- **Enable/Disable OctoPrint Service:**\
|
||||
Usefull when using DWC2/Mainsail/Fluidd and OctoPrint at the same time to prevent them interfering with each other
|
||||
|
||||
- **Installing a G-Code Shell Command extension:**\
|
||||
For further information about that extension please see the [G-Code Shell Command Extension Doc](gcode_shell_command.md)
|
||||
|
||||
- **Uploading logfiles:**\
|
||||
You can directly upload logfiles like klippy.log, moonraker.log and dwc2.log from the KIAUH main menu for providing them for troubleshooting purposes.
|
||||
|
||||
|
||||
to be continued...
|
||||
@@ -41,7 +41,7 @@ Execute with:
|
||||
`RUN_SHELL_COMMAND CMD=hello_world`
|
||||
|
||||
### Passing parameters:
|
||||
As of commit [f231fa9](https://github.com/th33xitus/kiauh/commit/f231fa9c69191f23277b4e3319f6b675bfa0ee42) it is also possible to pass optional parameters to a `gcode_shell_command`.
|
||||
As of commit [f231fa9](https://github.com/dw-0/kiauh/commit/f231fa9c69191f23277b4e3319f6b675bfa0ee42) it is also possible to pass optional parameters to a `gcode_shell_command`.
|
||||
The following short example shows storing the extruder temperature into a variable, passing that value with a parameter to a `gcode_shell_command`, which then,
|
||||
once the gcode_macro runs and the gcode_shell_command gets called, executes the `script.sh`. The script then echoes a message to the console (if `verbose: True`)
|
||||
and writes the value of the parameter into a textfile called `test.txt` located in the home directory.
|
||||
|
||||
16
kiauh.cfg.example
Normal file
16
kiauh.cfg.example
Normal file
@@ -0,0 +1,16 @@
|
||||
[kiauh]
|
||||
backup_before_update: False
|
||||
|
||||
[klipper]
|
||||
repository_url: https://github.com/Klipper3d/klipper
|
||||
branch: master
|
||||
method: https
|
||||
|
||||
[moonraker]
|
||||
repository_url: https://github.com/Arksine/moonraker
|
||||
branch: master
|
||||
method: https
|
||||
|
||||
[mainsail]
|
||||
default_port: 80
|
||||
unstable_releases: False
|
||||
15
kiauh.py
Normal file
15
kiauh.py
Normal file
@@ -0,0 +1,15 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# ======================================================================= #
|
||||
# Copyright (C) 2020 - 2023 Dominik Willner <th33xitus@gmail.com> #
|
||||
# #
|
||||
# This file is part of KIAUH - Klipper Installation And Update Helper #
|
||||
# https://github.com/dw-0/kiauh #
|
||||
# #
|
||||
# This file may be distributed under the terms of the GNU GPLv3 license #
|
||||
# ======================================================================= #
|
||||
|
||||
from kiauh.main import main
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
199
kiauh.sh
199
kiauh.sh
@@ -1,105 +1,108 @@
|
||||
#!/bin/bash
|
||||
clear
|
||||
#!/usr/bin/env bash
|
||||
|
||||
#=======================================================================#
|
||||
# Copyright (C) 2020 - 2023 Dominik Willner <th33xitus@gmail.com> #
|
||||
# #
|
||||
# This file is part of KIAUH - Klipper Installation And Update Helper #
|
||||
# https://github.com/dw-0/kiauh #
|
||||
# #
|
||||
# This file may be distributed under the terms of the GNU GPLv3 license #
|
||||
#=======================================================================#
|
||||
|
||||
set -e
|
||||
clear
|
||||
|
||||
### set color variables
|
||||
green=$(echo -en "\e[92m")
|
||||
yellow=$(echo -en "\e[93m")
|
||||
red=$(echo -en "\e[91m")
|
||||
cyan=$(echo -en "\e[96m")
|
||||
default=$(echo -en "\e[39m")
|
||||
function main() {
|
||||
local python_command
|
||||
local entrypoint
|
||||
|
||||
### sourcing all additional scripts
|
||||
SRCDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )"/.. && pwd )"
|
||||
for script in "${SRCDIR}/kiauh/scripts/"*.sh; do . $script; done
|
||||
for script in "${SRCDIR}/kiauh/scripts/ui/"*.sh; do . $script; done
|
||||
|
||||
### set important directories
|
||||
#klipper
|
||||
KLIPPER_DIR=${HOME}/klipper
|
||||
KLIPPY_ENV=${HOME}/klippy-env
|
||||
#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=${HOME}/moonraker-env
|
||||
#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}/duetwebcontrol
|
||||
#octoprint
|
||||
OCTOPRINT_DIR=${HOME}/OctoPrint
|
||||
OCTOPRINT_CFG_DIR=${HOME}/.octoprint
|
||||
#KlipperScreen
|
||||
KLIPPERSCREEN_DIR=${HOME}/KlipperScreen
|
||||
KLIPPERSCREEN_ENV_DIR=${HOME}/.KlipperScreen-env
|
||||
#MoonrakerTelegramBot
|
||||
MOONRAKER_TELEGRAM_BOT_DIR=${HOME}/moonraker-telegram-bot
|
||||
MOONRAKER_TELEGRAM_BOT_ENV_DIR=${HOME}/moonraker-telegram-bot-env
|
||||
#misc
|
||||
INI_FILE=${HOME}/.kiauh.ini
|
||||
BACKUP_DIR=${HOME}/kiauh-backups
|
||||
|
||||
### set github repos
|
||||
KLIPPER_REPO=https://github.com/Klipper3d/klipper.git
|
||||
ARKSINE_REPO=https://github.com/Arksine/klipper.git
|
||||
DMBUTYUGIN_REPO=https://github.com/dmbutyugin/klipper.git
|
||||
DWC2FK_REPO=https://github.com/Stephan3/dwc2-for-klipper-socket.git
|
||||
KLIPPERSCREEN_REPO=https://github.com/jordanruthe/KlipperScreen.git
|
||||
NLEF_REPO=https://github.com/nlef/moonraker-telegram-bot.git
|
||||
#branches
|
||||
BRANCH_SCURVE_SMOOTHING=dmbutyugin/scurve-smoothing
|
||||
BRANCH_SCURVE_SHAPING=dmbutyugin/scurve-shaping
|
||||
|
||||
### set some messages
|
||||
warn_msg(){
|
||||
echo -e "${red}<!!!!> $1${default}"
|
||||
}
|
||||
status_msg(){
|
||||
echo; echo -e "${yellow}###### $1${default}"
|
||||
}
|
||||
ok_msg(){
|
||||
echo -e "${green}>>>>>> $1${default}"
|
||||
}
|
||||
title_msg(){
|
||||
echo -e "${cyan}$1${default}"
|
||||
}
|
||||
get_date(){
|
||||
current_date=$(date +"%y%m%d-%H%M")
|
||||
}
|
||||
print_unkown_cmd(){
|
||||
ERROR_MSG="Invalid command!"
|
||||
}
|
||||
|
||||
print_msg(){
|
||||
if [[ "$ERROR_MSG" != "" ]]; then
|
||||
echo -e "${red}"
|
||||
echo -e "#########################################################"
|
||||
echo -e " $ERROR_MSG "
|
||||
echo -e "#########################################################"
|
||||
echo -e "${default}"
|
||||
fi
|
||||
if [ "$CONFIRM_MSG" != "" ]; then
|
||||
echo -e "${green}"
|
||||
echo -e "#########################################################"
|
||||
echo -e " $CONFIRM_MSG "
|
||||
echo -e "#########################################################"
|
||||
echo -e "${default}"
|
||||
if command -v python3 &>/dev/null; then
|
||||
python_command="python3"
|
||||
elif command -v python &>/dev/null; then
|
||||
python_command="python"
|
||||
else
|
||||
echo "Python is not installed. Please install Python and try again."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
entrypoint=$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")
|
||||
|
||||
${python_command} "${entrypoint}/kiauh.py"
|
||||
}
|
||||
|
||||
clear_msg(){
|
||||
unset CONFIRM_MSG
|
||||
unset ERROR_MSG
|
||||
}
|
||||
main
|
||||
|
||||
check_euid
|
||||
init_ini
|
||||
kiauh_status
|
||||
main_menu
|
||||
#### sourcing all additional scripts
|
||||
#KIAUH_SRCDIR="$(dirname -- "$(readlink -f "${BASH_SOURCE[0]}")")"
|
||||
#for script in "${KIAUH_SRCDIR}/scripts/"*.sh; do . "${script}"; done
|
||||
#for script in "${KIAUH_SRCDIR}/scripts/ui/"*.sh; do . "${script}"; done
|
||||
#
|
||||
##===================================================#
|
||||
##=================== UPDATE KIAUH ==================#
|
||||
##===================================================#
|
||||
#
|
||||
#function update_kiauh() {
|
||||
# status_msg "Updating KIAUH ..."
|
||||
#
|
||||
# cd "${KIAUH_SRCDIR}"
|
||||
# git reset --hard && git pull
|
||||
#
|
||||
# ok_msg "Update complete! Please restart KIAUH."
|
||||
# exit 0
|
||||
#}
|
||||
#
|
||||
##===================================================#
|
||||
##=================== KIAUH STATUS ==================#
|
||||
##===================================================#
|
||||
#
|
||||
#function kiauh_update_avail() {
|
||||
# [[ ! -d "${KIAUH_SRCDIR}/.git" ]] && return
|
||||
# local origin head
|
||||
#
|
||||
# cd "${KIAUH_SRCDIR}"
|
||||
#
|
||||
# ### abort if not on master branch
|
||||
# ! git branch -a | grep -q "\* master" && return
|
||||
#
|
||||
# ### compare commit hash
|
||||
# git fetch -q
|
||||
# origin=$(git rev-parse --short=8 origin/master)
|
||||
# head=$(git rev-parse --short=8 HEAD)
|
||||
#
|
||||
# if [[ ${origin} != "${head}" ]]; then
|
||||
# echo "true"
|
||||
# fi
|
||||
#}
|
||||
#
|
||||
#function kiauh_update_dialog() {
|
||||
# [[ ! $(kiauh_update_avail) == "true" ]] && return
|
||||
# top_border
|
||||
# echo -e "|${green} New KIAUH update available! ${white}|"
|
||||
# hr
|
||||
# echo -e "|${green} View Changelog: https://git.io/JnmlX ${white}|"
|
||||
# blank_line
|
||||
# echo -e "|${yellow} It is recommended to keep KIAUH up to date. Updates ${white}|"
|
||||
# echo -e "|${yellow} usually contain bugfixes, important changes or new ${white}|"
|
||||
# echo -e "|${yellow} features. Please consider updating! ${white}|"
|
||||
# bottom_border
|
||||
#
|
||||
# local yn
|
||||
# read -p "${cyan}###### Do you want to update now? (Y/n):${white} " yn
|
||||
# while true; do
|
||||
# case "${yn}" in
|
||||
# Y|y|Yes|yes|"")
|
||||
# do_action "update_kiauh"
|
||||
# break;;
|
||||
# N|n|No|no)
|
||||
# break;;
|
||||
# *)
|
||||
# deny_action "kiauh_update_dialog";;
|
||||
# esac
|
||||
# done
|
||||
#}
|
||||
#
|
||||
#check_euid
|
||||
#init_logfile
|
||||
#set_globals
|
||||
#kiauh_update_dialog
|
||||
#main_menu
|
||||
|
||||
17
kiauh/__init__.py
Normal file
17
kiauh/__init__.py
Normal file
@@ -0,0 +1,17 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# ======================================================================= #
|
||||
# Copyright (C) 2020 - 2023 Dominik Willner <th33xitus@gmail.com> #
|
||||
# #
|
||||
# This file is part of KIAUH - Klipper Installation And Update Helper #
|
||||
# https://github.com/dw-0/kiauh #
|
||||
# #
|
||||
# This file may be distributed under the terms of the GNU GPLv3 license #
|
||||
# ======================================================================= #
|
||||
|
||||
from os.path import join, dirname, abspath
|
||||
from pathlib import Path
|
||||
|
||||
APPLICATION_ROOT = dirname(dirname(abspath(__file__)))
|
||||
KIAUH_CFG = join(APPLICATION_ROOT, "kiauh.cfg")
|
||||
KIAUH_BACKUP_DIR = f"{Path.home()}/kiauh-backups"
|
||||
0
kiauh/core/__init__.py
Normal file
0
kiauh/core/__init__.py
Normal file
0
kiauh/core/backup_manager/__init__.py
Normal file
0
kiauh/core/backup_manager/__init__.py
Normal file
69
kiauh/core/backup_manager/backup_manager.py
Normal file
69
kiauh/core/backup_manager/backup_manager.py
Normal file
@@ -0,0 +1,69 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# ======================================================================= #
|
||||
# Copyright (C) 2020 - 2023 Dominik Willner <th33xitus@gmail.com> #
|
||||
# #
|
||||
# This file is part of KIAUH - Klipper Installation And Update Helper #
|
||||
# https://github.com/dw-0/kiauh #
|
||||
# #
|
||||
# This file may be distributed under the terms of the GNU GPLv3 license #
|
||||
# ======================================================================= #
|
||||
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
|
||||
from kiauh import KIAUH_BACKUP_DIR
|
||||
from kiauh.utils.common import get_current_date
|
||||
from kiauh.utils.logger import Logger
|
||||
|
||||
|
||||
# noinspection PyMethodMayBeStatic
|
||||
class BackupManager:
|
||||
def __init__(
|
||||
self, backup_name: str, source: Path, backup_dir: Path = KIAUH_BACKUP_DIR
|
||||
):
|
||||
self._backup_name = backup_name
|
||||
self._source = source
|
||||
self._backup_dir = backup_dir
|
||||
|
||||
@property
|
||||
def backup_name(self) -> str:
|
||||
return self._backup_name
|
||||
|
||||
@backup_name.setter
|
||||
def backup_name(self, value: str):
|
||||
self._backup_name = value
|
||||
|
||||
@property
|
||||
def source(self) -> Path:
|
||||
return self._source
|
||||
|
||||
@source.setter
|
||||
def source(self, value: Path):
|
||||
self._source = value
|
||||
|
||||
@property
|
||||
def backup_dir(self) -> Path:
|
||||
return self._backup_dir
|
||||
|
||||
@backup_dir.setter
|
||||
def backup_dir(self, value: Path):
|
||||
self._backup_dir = value
|
||||
|
||||
def backup(self) -> None:
|
||||
if self._source is None or not Path(self._source).exists():
|
||||
raise OSError
|
||||
|
||||
try:
|
||||
log = f"Creating backup of {self.backup_name} in {self.backup_dir} ..."
|
||||
Logger.print_status(log)
|
||||
date = get_current_date()
|
||||
dest = Path(
|
||||
f"{self.backup_dir}/{self.backup_name}/{date.get('date')}-{date.get('time')}"
|
||||
)
|
||||
shutil.copytree(src=self.source, dst=dest)
|
||||
except OSError as e:
|
||||
Logger.print_error(f"Unable to backup source directory. Not exist.\n{e}")
|
||||
return
|
||||
|
||||
Logger.print_ok("Backup successfull!")
|
||||
0
kiauh/core/config_manager/__init__.py
Normal file
0
kiauh/core/config_manager/__init__.py
Normal file
81
kiauh/core/config_manager/config_manager.py
Normal file
81
kiauh/core/config_manager/config_manager.py
Normal file
@@ -0,0 +1,81 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# ======================================================================= #
|
||||
# Copyright (C) 2020 - 2023 Dominik Willner <th33xitus@gmail.com> #
|
||||
# #
|
||||
# This file is part of KIAUH - Klipper Installation And Update Helper #
|
||||
# https://github.com/dw-0/kiauh #
|
||||
# #
|
||||
# This file may be distributed under the terms of the GNU GPLv3 license #
|
||||
# ======================================================================= #
|
||||
|
||||
import configparser
|
||||
from typing import Union
|
||||
|
||||
from kiauh.utils.logger import Logger
|
||||
|
||||
|
||||
# noinspection PyMethodMayBeStatic
|
||||
class ConfigManager:
|
||||
def __init__(self, cfg_file: str):
|
||||
self.config_file = cfg_file
|
||||
self.config = CustomConfigParser()
|
||||
|
||||
def read_config(self) -> None:
|
||||
if not self.config_file:
|
||||
Logger.print_error("Unable to read config file. File not found.")
|
||||
return
|
||||
|
||||
self.config.read_file(open(self.config_file, "r"))
|
||||
|
||||
def write_config(self) -> None:
|
||||
with open(self.config_file, "w") as cfg:
|
||||
self.config.write(cfg)
|
||||
|
||||
def get_value(self, section: str, key: str, silent=False) -> Union[str, bool, None]:
|
||||
if not self.config.has_section(section):
|
||||
if not silent:
|
||||
log = f"Section not defined. Unable to read section: [{section}]."
|
||||
Logger.print_error(log)
|
||||
return None
|
||||
|
||||
if not self.config.has_option(section, key):
|
||||
if not silent:
|
||||
log = f"Option not defined in section [{section}]. Unable to read option: '{key}'."
|
||||
Logger.print_error(log)
|
||||
return None
|
||||
|
||||
value = self.config.get(section, key)
|
||||
if value == "True" or value == "true":
|
||||
return True
|
||||
elif value == "False" or value == "false":
|
||||
return False
|
||||
else:
|
||||
return value
|
||||
|
||||
def set_value(self, section: str, key: str, value: str):
|
||||
self.config.set(section, key, value)
|
||||
|
||||
|
||||
class CustomConfigParser(configparser.ConfigParser):
|
||||
"""
|
||||
A custom ConfigParser class overwriting the write() method of configparser.Configparser.
|
||||
Key and value will be delimited by a ": ".
|
||||
Note the whitespace AFTER the colon, which is the whole reason for that overwrite.
|
||||
"""
|
||||
|
||||
def write(self, fp, space_around_delimiters=False):
|
||||
if self._defaults:
|
||||
fp.write("[%s]\n" % configparser.DEFAULTSECT)
|
||||
for key, value in self._defaults.items():
|
||||
fp.write("%s: %s\n" % (key, str(value).replace("\n", "\n\t")))
|
||||
fp.write("\n")
|
||||
for section in self._sections:
|
||||
fp.write("[%s]\n" % section)
|
||||
for key, value in self._sections[section].items():
|
||||
if key == "__name__":
|
||||
continue
|
||||
if (value is not None) or (self._optcre == self.OPTCRE):
|
||||
key = ": ".join((key, str(value).replace("\n", "\n\t")))
|
||||
fp.write("%s\n" % key)
|
||||
fp.write("\n")
|
||||
0
kiauh/core/instance_manager/__init__.py
Normal file
0
kiauh/core/instance_manager/__init__.py
Normal file
162
kiauh/core/instance_manager/base_instance.py
Normal file
162
kiauh/core/instance_manager/base_instance.py
Normal file
@@ -0,0 +1,162 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# ======================================================================= #
|
||||
# Copyright (C) 2020 - 2023 Dominik Willner <th33xitus@gmail.com> #
|
||||
# #
|
||||
# This file is part of KIAUH - Klipper Installation And Update Helper #
|
||||
# https://github.com/dw-0/kiauh #
|
||||
# #
|
||||
# This file may be distributed under the terms of the GNU GPLv3 license #
|
||||
# ======================================================================= #
|
||||
|
||||
from abc import abstractmethod, ABC
|
||||
from pathlib import Path
|
||||
from typing import List, Union, Optional, Type, TypeVar
|
||||
|
||||
from kiauh.utils.constants import SYSTEMD, CURRENT_USER
|
||||
from kiauh.utils.filesystem_utils import create_directory
|
||||
|
||||
B = TypeVar(name="B", bound="BaseInstance", covariant=True)
|
||||
|
||||
|
||||
class BaseInstance(ABC):
|
||||
@classmethod
|
||||
def blacklist(cls) -> List[str]:
|
||||
return []
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
suffix: Optional[str],
|
||||
instance_type: B = B,
|
||||
):
|
||||
self._instance_type = instance_type
|
||||
self._suffix = suffix
|
||||
self._user = CURRENT_USER
|
||||
self._data_dir_name = self.get_data_dir_from_suffix()
|
||||
self._data_dir = f"{Path.home()}/{self._data_dir_name}_data"
|
||||
self._cfg_dir = f"{self.data_dir}/config"
|
||||
self._log_dir = f"{self.data_dir}/logs"
|
||||
self._comms_dir = f"{self.data_dir}/comms"
|
||||
self._sysd_dir = f"{self.data_dir}/systemd"
|
||||
self._gcodes_dir = f"{self.data_dir}/gcodes"
|
||||
|
||||
@property
|
||||
def instance_type(self) -> Type["BaseInstance"]:
|
||||
return self._instance_type
|
||||
|
||||
@instance_type.setter
|
||||
def instance_type(self, value: Type["BaseInstance"]) -> None:
|
||||
self._instance_type = value
|
||||
|
||||
@property
|
||||
def suffix(self) -> str:
|
||||
return self._suffix
|
||||
|
||||
@suffix.setter
|
||||
def suffix(self, value: Union[str, None]) -> None:
|
||||
self._suffix = value
|
||||
|
||||
@property
|
||||
def user(self) -> str:
|
||||
return self._user
|
||||
|
||||
@user.setter
|
||||
def user(self, value: str) -> None:
|
||||
self._user = value
|
||||
|
||||
@property
|
||||
def data_dir_name(self) -> str:
|
||||
return self._data_dir_name
|
||||
|
||||
@data_dir_name.setter
|
||||
def data_dir_name(self, value: str) -> None:
|
||||
self._data_dir_name = value
|
||||
|
||||
@property
|
||||
def data_dir(self):
|
||||
return self._data_dir
|
||||
|
||||
@data_dir.setter
|
||||
def data_dir(self, value: str):
|
||||
self._data_dir = value
|
||||
|
||||
@property
|
||||
def cfg_dir(self):
|
||||
return self._cfg_dir
|
||||
|
||||
@cfg_dir.setter
|
||||
def cfg_dir(self, value: str):
|
||||
self._cfg_dir = value
|
||||
|
||||
@property
|
||||
def log_dir(self):
|
||||
return self._log_dir
|
||||
|
||||
@log_dir.setter
|
||||
def log_dir(self, value: str):
|
||||
self._log_dir = value
|
||||
|
||||
@property
|
||||
def comms_dir(self):
|
||||
return self._comms_dir
|
||||
|
||||
@comms_dir.setter
|
||||
def comms_dir(self, value: str):
|
||||
self._comms_dir = value
|
||||
|
||||
@property
|
||||
def sysd_dir(self):
|
||||
return self._sysd_dir
|
||||
|
||||
@sysd_dir.setter
|
||||
def sysd_dir(self, value: str):
|
||||
self._sysd_dir = value
|
||||
|
||||
@property
|
||||
def gcodes_dir(self):
|
||||
return self._gcodes_dir
|
||||
|
||||
@gcodes_dir.setter
|
||||
def gcodes_dir(self, value: str):
|
||||
self._gcodes_dir = value
|
||||
|
||||
@abstractmethod
|
||||
def create(self) -> None:
|
||||
raise NotImplementedError("Subclasses must implement the create method")
|
||||
|
||||
@abstractmethod
|
||||
def delete(self, del_remnants: bool) -> None:
|
||||
raise NotImplementedError("Subclasses must implement the delete method")
|
||||
|
||||
def create_folders(self, add_dirs: List[str] = None) -> None:
|
||||
dirs = [
|
||||
self.data_dir,
|
||||
self.cfg_dir,
|
||||
self.log_dir,
|
||||
self.comms_dir,
|
||||
self.sysd_dir,
|
||||
]
|
||||
|
||||
if add_dirs:
|
||||
dirs.extend(add_dirs)
|
||||
|
||||
for _dir in dirs:
|
||||
create_directory(Path(_dir))
|
||||
|
||||
def get_service_file_name(self, extension: bool = False) -> str:
|
||||
name = f"{self.__class__.__name__.lower()}"
|
||||
if self.suffix is not None:
|
||||
name += f"-{self.suffix}"
|
||||
|
||||
return name if not extension else f"{name}.service"
|
||||
|
||||
def get_service_file_path(self) -> str:
|
||||
return f"{SYSTEMD}/{self.get_service_file_name(extension=True)}"
|
||||
|
||||
def get_data_dir_from_suffix(self) -> str:
|
||||
if self._suffix is None:
|
||||
return "printer"
|
||||
elif self._suffix.isdigit():
|
||||
return f"printer_{self._suffix}"
|
||||
else:
|
||||
return self._suffix
|
||||
218
kiauh/core/instance_manager/instance_manager.py
Normal file
218
kiauh/core/instance_manager/instance_manager.py
Normal file
@@ -0,0 +1,218 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# ======================================================================= #
|
||||
# Copyright (C) 2020 - 2023 Dominik Willner <th33xitus@gmail.com> #
|
||||
# #
|
||||
# This file is part of KIAUH - Klipper Installation And Update Helper #
|
||||
# https://github.com/dw-0/kiauh #
|
||||
# #
|
||||
# This file may be distributed under the terms of the GNU GPLv3 license #
|
||||
# ======================================================================= #
|
||||
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
from typing import List, Optional, Union, TypeVar
|
||||
|
||||
from kiauh.core.instance_manager.base_instance import BaseInstance
|
||||
from kiauh.utils.constants import SYSTEMD
|
||||
from kiauh.utils.logger import Logger
|
||||
|
||||
|
||||
I = TypeVar(name="I", bound=BaseInstance, covariant=True)
|
||||
|
||||
|
||||
# noinspection PyMethodMayBeStatic
|
||||
class InstanceManager:
|
||||
def __init__(self, instance_type: I) -> None:
|
||||
self._instance_type = instance_type
|
||||
self._current_instance: Optional[I] = None
|
||||
self._instance_suffix: Optional[str] = None
|
||||
self._instance_service: Optional[str] = None
|
||||
self._instance_service_full: Optional[str] = None
|
||||
self._instance_service_path: Optional[str] = None
|
||||
self._instances: List[I] = []
|
||||
|
||||
@property
|
||||
def instance_type(self) -> I:
|
||||
return self._instance_type
|
||||
|
||||
@instance_type.setter
|
||||
def instance_type(self, value: I):
|
||||
self._instance_type = value
|
||||
|
||||
@property
|
||||
def current_instance(self) -> I:
|
||||
return self._current_instance
|
||||
|
||||
@current_instance.setter
|
||||
def current_instance(self, value: I) -> None:
|
||||
self._current_instance = value
|
||||
self.instance_suffix = value.suffix
|
||||
self.instance_service = value.get_service_file_name()
|
||||
self.instance_service_path = value.get_service_file_path()
|
||||
|
||||
@property
|
||||
def instance_suffix(self) -> str:
|
||||
return self._instance_suffix
|
||||
|
||||
@instance_suffix.setter
|
||||
def instance_suffix(self, value: str):
|
||||
self._instance_suffix = value
|
||||
|
||||
@property
|
||||
def instance_service(self) -> str:
|
||||
return self._instance_service
|
||||
|
||||
@instance_service.setter
|
||||
def instance_service(self, value: str):
|
||||
self._instance_service = value
|
||||
|
||||
@property
|
||||
def instance_service_full(self) -> str:
|
||||
return f"{self._instance_service}.service"
|
||||
|
||||
@property
|
||||
def instance_service_path(self) -> str:
|
||||
return self._instance_service_path
|
||||
|
||||
@instance_service_path.setter
|
||||
def instance_service_path(self, value: str):
|
||||
self._instance_service_path = value
|
||||
|
||||
@property
|
||||
def instances(self) -> List[I]:
|
||||
if not self._instances:
|
||||
self._instances = self._find_instances()
|
||||
|
||||
return sorted(self._instances, key=lambda x: self._sort_instance_list(x.suffix))
|
||||
|
||||
@instances.setter
|
||||
def instances(self, value: List[I]):
|
||||
self._instances = value
|
||||
|
||||
def create_instance(self) -> None:
|
||||
if self.current_instance is not None:
|
||||
try:
|
||||
self.current_instance.create()
|
||||
except (OSError, subprocess.CalledProcessError) as e:
|
||||
Logger.print_error(f"Creating instance failed: {e}")
|
||||
raise
|
||||
else:
|
||||
raise ValueError("current_instance cannot be None")
|
||||
|
||||
def delete_instance(self, del_remnants=False) -> None:
|
||||
if self.current_instance is not None:
|
||||
try:
|
||||
self.current_instance.delete(del_remnants)
|
||||
except (OSError, subprocess.CalledProcessError) as e:
|
||||
Logger.print_error(f"Removing instance failed: {e}")
|
||||
raise
|
||||
else:
|
||||
raise ValueError("current_instance cannot be None")
|
||||
|
||||
def enable_instance(self) -> None:
|
||||
Logger.print_status(f"Enabling {self.instance_service_full} ...")
|
||||
try:
|
||||
command = ["sudo", "systemctl", "enable", self.instance_service_full]
|
||||
if subprocess.run(command, check=True):
|
||||
Logger.print_ok(f"{self.instance_service_full} enabled.")
|
||||
except subprocess.CalledProcessError as e:
|
||||
Logger.print_error(f"Error enabling service {self.instance_service_full}:")
|
||||
Logger.print_error(f"{e}")
|
||||
|
||||
def disable_instance(self) -> None:
|
||||
Logger.print_status(f"Disabling {self.instance_service_full} ...")
|
||||
try:
|
||||
command = ["sudo", "systemctl", "disable", self.instance_service_full]
|
||||
if subprocess.run(command, check=True):
|
||||
Logger.print_ok(f"{self.instance_service_full} disabled.")
|
||||
except subprocess.CalledProcessError as e:
|
||||
Logger.print_error(f"Error disabling {self.instance_service_full}:")
|
||||
Logger.print_error(f"{e}")
|
||||
|
||||
def start_instance(self) -> None:
|
||||
Logger.print_status(f"Starting {self.instance_service_full} ...")
|
||||
try:
|
||||
command = ["sudo", "systemctl", "start", self.instance_service_full]
|
||||
if subprocess.run(command, check=True):
|
||||
Logger.print_ok(f"{self.instance_service_full} started.")
|
||||
except subprocess.CalledProcessError as e:
|
||||
Logger.print_error(f"Error starting {self.instance_service_full}:")
|
||||
Logger.print_error(f"{e}")
|
||||
|
||||
def restart_instance(self) -> None:
|
||||
Logger.print_status(f"Restarting {self.instance_service_full} ...")
|
||||
try:
|
||||
command = ["sudo", "systemctl", "restart", self.instance_service_full]
|
||||
if subprocess.run(command, check=True):
|
||||
Logger.print_ok(f"{self.instance_service_full} restarted.")
|
||||
except subprocess.CalledProcessError as e:
|
||||
Logger.print_error(f"Error restarting {self.instance_service_full}:")
|
||||
Logger.print_error(f"{e}")
|
||||
|
||||
def start_all_instance(self) -> None:
|
||||
for instance in self.instances:
|
||||
self.current_instance = instance
|
||||
self.start_instance()
|
||||
|
||||
def restart_all_instance(self) -> None:
|
||||
for instance in self.instances:
|
||||
self.current_instance = instance
|
||||
self.restart_instance()
|
||||
|
||||
def stop_instance(self) -> None:
|
||||
Logger.print_status(f"Stopping {self.instance_service_full} ...")
|
||||
try:
|
||||
command = ["sudo", "systemctl", "stop", self.instance_service_full]
|
||||
if subprocess.run(command, check=True):
|
||||
Logger.print_ok(f"{self.instance_service_full} stopped.")
|
||||
except subprocess.CalledProcessError as e:
|
||||
Logger.print_error(f"Error stopping {self.instance_service_full}:")
|
||||
Logger.print_error(f"{e}")
|
||||
raise
|
||||
|
||||
def stop_all_instance(self) -> None:
|
||||
for instance in self.instances:
|
||||
self.current_instance = instance
|
||||
self.stop_instance()
|
||||
|
||||
def reload_daemon(self) -> None:
|
||||
Logger.print_status("Reloading systemd manager configuration ...")
|
||||
try:
|
||||
command = ["sudo", "systemctl", "daemon-reload"]
|
||||
if subprocess.run(command, check=True):
|
||||
Logger.print_ok("Systemd manager configuration reloaded")
|
||||
except subprocess.CalledProcessError as e:
|
||||
Logger.print_error("Error reloading systemd manager configuration:")
|
||||
Logger.print_error(f"{e}")
|
||||
raise
|
||||
|
||||
def _find_instances(self) -> List[I]:
|
||||
name = self.instance_type.__name__.lower()
|
||||
pattern = re.compile(f"^{name}(-[0-9a-zA-Z]+)?.service$")
|
||||
excluded = self.instance_type.blacklist()
|
||||
|
||||
service_list = [
|
||||
os.path.join(SYSTEMD, service)
|
||||
for service in os.listdir(SYSTEMD)
|
||||
if pattern.search(service) and not any(s in service for s in excluded)
|
||||
]
|
||||
|
||||
instance_list = [
|
||||
self.instance_type(suffix=self._get_instance_suffix(service))
|
||||
for service in service_list
|
||||
]
|
||||
|
||||
return instance_list
|
||||
|
||||
def _get_instance_suffix(self, file_path: str) -> Union[str, None]:
|
||||
full_name = file_path.split("/")[-1].split(".")[0]
|
||||
|
||||
return full_name.split("-")[-1] if "-" in full_name else None
|
||||
|
||||
def _sort_instance_list(self, s: Union[int, str, None]):
|
||||
if s is None:
|
||||
return
|
||||
|
||||
return int(s) if s.isdigit() else s
|
||||
14
kiauh/core/menus/__init__.py
Normal file
14
kiauh/core/menus/__init__.py
Normal file
@@ -0,0 +1,14 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# ======================================================================= #
|
||||
# Copyright (C) 2020 - 2023 Dominik Willner <th33xitus@gmail.com> #
|
||||
# #
|
||||
# This file is part of KIAUH - Klipper Installation And Update Helper #
|
||||
# https://github.com/dw-0/kiauh #
|
||||
# #
|
||||
# This file may be distributed under the terms of the GNU GPLv3 license #
|
||||
# ======================================================================= #
|
||||
|
||||
QUIT_FOOTER = "quit"
|
||||
BACK_FOOTER = "back"
|
||||
BACK_HELP_FOOTER = "back_help"
|
||||
42
kiauh/core/menus/advanced_menu.py
Normal file
42
kiauh/core/menus/advanced_menu.py
Normal file
@@ -0,0 +1,42 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# ======================================================================= #
|
||||
# Copyright (C) 2020 - 2023 Dominik Willner <th33xitus@gmail.com> #
|
||||
# #
|
||||
# This file is part of KIAUH - Klipper Installation And Update Helper #
|
||||
# https://github.com/dw-0/kiauh #
|
||||
# #
|
||||
# This file may be distributed under the terms of the GNU GPLv3 license #
|
||||
# ======================================================================= #
|
||||
|
||||
import textwrap
|
||||
|
||||
from kiauh.core.menus import BACK_FOOTER
|
||||
from kiauh.core.menus.base_menu import BaseMenu
|
||||
from kiauh.utils.constants import COLOR_YELLOW, RESET_FORMAT
|
||||
|
||||
|
||||
class AdvancedMenu(BaseMenu):
|
||||
def __init__(self):
|
||||
super().__init__(header=True, options={}, footer_type=BACK_FOOTER)
|
||||
|
||||
def print_menu(self):
|
||||
header = " [ Advanced Menu ] "
|
||||
color = COLOR_YELLOW
|
||||
count = 62 - len(color) - len(RESET_FORMAT)
|
||||
menu = textwrap.dedent(
|
||||
f"""
|
||||
/=======================================================\\
|
||||
| {color}{header:~^{count}}{RESET_FORMAT} |
|
||||
|-------------------------------------------------------|
|
||||
| Klipper & API: | Mainsail: |
|
||||
| 0) [Rollback] | 5) [Theme installer] |
|
||||
| | |
|
||||
| Firmware: | System: |
|
||||
| 1) [Build only] | 6) [Change hostname] |
|
||||
| 2) [Flash only] | |
|
||||
| 3) [Build + Flash] | Extras: |
|
||||
| 4) [Get MCU ID] | 7) [G-Code Shell Command] |
|
||||
"""
|
||||
)[1:]
|
||||
print(menu, end="")
|
||||
189
kiauh/core/menus/base_menu.py
Normal file
189
kiauh/core/menus/base_menu.py
Normal file
@@ -0,0 +1,189 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# ======================================================================= #
|
||||
# Copyright (C) 2020 - 2023 Dominik Willner <th33xitus@gmail.com> #
|
||||
# #
|
||||
# This file is part of KIAUH - Klipper Installation And Update Helper #
|
||||
# https://github.com/dw-0/kiauh #
|
||||
# #
|
||||
# This file may be distributed under the terms of the GNU GPLv3 license #
|
||||
# ======================================================================= #
|
||||
|
||||
import subprocess
|
||||
import sys
|
||||
import textwrap
|
||||
from abc import abstractmethod, ABC
|
||||
from typing import Dict, Any, Literal
|
||||
|
||||
from kiauh.core.menus import QUIT_FOOTER, BACK_FOOTER, BACK_HELP_FOOTER
|
||||
from kiauh.utils.constants import (
|
||||
COLOR_GREEN,
|
||||
COLOR_YELLOW,
|
||||
COLOR_RED,
|
||||
COLOR_CYAN,
|
||||
RESET_FORMAT,
|
||||
)
|
||||
|
||||
|
||||
def clear():
|
||||
subprocess.call("clear", shell=True)
|
||||
|
||||
|
||||
def print_header():
|
||||
line1 = " [ KIAUH ] "
|
||||
line2 = "Klipper Installation And Update Helper"
|
||||
line3 = ""
|
||||
color = COLOR_CYAN
|
||||
count = 62 - len(color) - len(RESET_FORMAT)
|
||||
header = textwrap.dedent(
|
||||
f"""
|
||||
/=======================================================\\
|
||||
| {color}{line1:~^{count}}{RESET_FORMAT} |
|
||||
| {color}{line2:^{count}}{RESET_FORMAT} |
|
||||
| {color}{line3:~^{count}}{RESET_FORMAT} |
|
||||
\=======================================================/
|
||||
"""
|
||||
)[1:]
|
||||
print(header, end="")
|
||||
|
||||
|
||||
def print_quit_footer():
|
||||
text = "Q) Quit"
|
||||
color = COLOR_RED
|
||||
count = 62 - len(color) - len(RESET_FORMAT)
|
||||
footer = textwrap.dedent(
|
||||
f"""
|
||||
|-------------------------------------------------------|
|
||||
| {color}{text:^{count}}{RESET_FORMAT} |
|
||||
\=======================================================/
|
||||
"""
|
||||
)[1:]
|
||||
print(footer, end="")
|
||||
|
||||
|
||||
def print_back_footer():
|
||||
text = "B) « Back"
|
||||
color = COLOR_GREEN
|
||||
count = 62 - len(color) - len(RESET_FORMAT)
|
||||
footer = textwrap.dedent(
|
||||
f"""
|
||||
|-------------------------------------------------------|
|
||||
| {color}{text:^{count}}{RESET_FORMAT} |
|
||||
\=======================================================/
|
||||
"""
|
||||
)[1:]
|
||||
print(footer, end="")
|
||||
|
||||
|
||||
def print_back_help_footer():
|
||||
text1 = "B) « Back"
|
||||
text2 = "H) Help [?]"
|
||||
color1 = COLOR_GREEN
|
||||
color2 = COLOR_YELLOW
|
||||
count = 34 - len(color1) - len(RESET_FORMAT)
|
||||
footer = textwrap.dedent(
|
||||
f"""
|
||||
|-------------------------------------------------------|
|
||||
| {color1}{text1:^{count}}{RESET_FORMAT} | {color2}{text2:^{count}}{RESET_FORMAT} |
|
||||
\=======================================================/
|
||||
"""
|
||||
)[1:]
|
||||
print(footer, end="")
|
||||
|
||||
|
||||
class BaseMenu(ABC):
|
||||
def __init__(
|
||||
self,
|
||||
options: Dict[int, Any],
|
||||
options_offset: int = 0,
|
||||
header: bool = True,
|
||||
footer_type: Literal[
|
||||
"QUIT_FOOTER", "BACK_FOOTER", "BACK_HELP_FOOTER"
|
||||
] = QUIT_FOOTER,
|
||||
):
|
||||
self.options = options
|
||||
self.options_offset = options_offset
|
||||
self.header = header
|
||||
self.footer_type = footer_type
|
||||
|
||||
@abstractmethod
|
||||
def print_menu(self):
|
||||
raise NotImplementedError("Subclasses must implement the print_menu method")
|
||||
|
||||
def print_footer(self):
|
||||
footer_type_map = {
|
||||
QUIT_FOOTER: print_quit_footer,
|
||||
BACK_FOOTER: print_back_footer,
|
||||
BACK_HELP_FOOTER: print_back_help_footer,
|
||||
}
|
||||
footer_function = footer_type_map.get(self.footer_type, print_quit_footer)
|
||||
footer_function()
|
||||
|
||||
def display(self):
|
||||
# clear()
|
||||
if self.header:
|
||||
print_header()
|
||||
self.print_menu()
|
||||
self.print_footer()
|
||||
|
||||
def handle_user_input(self):
|
||||
while True:
|
||||
choice = input(f"{COLOR_CYAN}###### Perform action: {RESET_FORMAT}")
|
||||
|
||||
error_msg = (
|
||||
f"{COLOR_RED}Invalid input.{RESET_FORMAT}"
|
||||
if choice.isalpha()
|
||||
else f"{COLOR_RED}Invalid input. Select a number between {min(self.options)} and {max(self.options)}.{RESET_FORMAT}"
|
||||
)
|
||||
|
||||
if choice.isdigit() and 0 <= int(choice) < len(self.options):
|
||||
return choice
|
||||
elif choice.isalpha():
|
||||
allowed_input = {
|
||||
"quit": ["q"],
|
||||
"back": ["b"],
|
||||
"back_help": ["b", "h"],
|
||||
}
|
||||
if (
|
||||
self.footer_type in allowed_input
|
||||
and choice.lower() in allowed_input[self.footer_type]
|
||||
):
|
||||
return choice
|
||||
else:
|
||||
print(error_msg)
|
||||
else:
|
||||
print(error_msg)
|
||||
|
||||
def start(self):
|
||||
while True:
|
||||
self.display()
|
||||
choice = self.handle_user_input()
|
||||
|
||||
if choice == "q":
|
||||
print(f"{COLOR_GREEN}###### Happy printing!{RESET_FORMAT}")
|
||||
sys.exit(0)
|
||||
elif choice == "b":
|
||||
return
|
||||
elif choice == "p":
|
||||
print("help!")
|
||||
else:
|
||||
self.execute_option(int(choice))
|
||||
|
||||
def execute_option(self, choice):
|
||||
option = self.options.get(choice, None)
|
||||
|
||||
if isinstance(option, type) and issubclass(option, BaseMenu):
|
||||
self.navigate_to_submenu(option)
|
||||
elif callable(option):
|
||||
option()
|
||||
elif option is None:
|
||||
raise NotImplementedError(f"No implementation for option {choice}")
|
||||
else:
|
||||
raise TypeError(
|
||||
f"Type {type(option)} of option {choice} not of type BaseMenu or Method"
|
||||
)
|
||||
|
||||
def navigate_to_submenu(self, submenu_class):
|
||||
submenu = submenu_class()
|
||||
submenu.previous_menu = self
|
||||
submenu.start()
|
||||
97
kiauh/core/menus/install_menu.py
Normal file
97
kiauh/core/menus/install_menu.py
Normal file
@@ -0,0 +1,97 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# ======================================================================= #
|
||||
# Copyright (C) 2020 - 2023 Dominik Willner <th33xitus@gmail.com> #
|
||||
# #
|
||||
# This file is part of KIAUH - Klipper Installation And Update Helper #
|
||||
# https://github.com/dw-0/kiauh #
|
||||
# #
|
||||
# This file may be distributed under the terms of the GNU GPLv3 license #
|
||||
# ======================================================================= #
|
||||
|
||||
import textwrap
|
||||
|
||||
from kiauh.core.menus import BACK_FOOTER
|
||||
from kiauh.core.menus.base_menu import BaseMenu
|
||||
from kiauh.modules.klipper import klipper_setup
|
||||
from kiauh.modules.mainsail import mainsail_setup
|
||||
from kiauh.modules.moonraker import moonraker_setup
|
||||
from kiauh.utils.constants import COLOR_GREEN, RESET_FORMAT
|
||||
|
||||
|
||||
# noinspection PyMethodMayBeStatic
|
||||
class InstallMenu(BaseMenu):
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
header=True,
|
||||
options={
|
||||
1: self.install_klipper,
|
||||
2: self.install_moonraker,
|
||||
3: self.install_mainsail,
|
||||
4: self.install_fluidd,
|
||||
5: self.install_klipperscreen,
|
||||
6: self.install_pretty_gcode,
|
||||
7: self.install_telegram_bot,
|
||||
8: self.install_obico,
|
||||
9: self.install_octoeverywhere,
|
||||
10: self.install_mobileraker,
|
||||
11: self.install_crowsnest,
|
||||
},
|
||||
footer_type=BACK_FOOTER,
|
||||
)
|
||||
|
||||
def print_menu(self):
|
||||
header = " [ Installation Menu ] "
|
||||
color = COLOR_GREEN
|
||||
count = 62 - len(color) - len(RESET_FORMAT)
|
||||
menu = textwrap.dedent(
|
||||
f"""
|
||||
/=======================================================\\
|
||||
| {color}{header:~^{count}}{RESET_FORMAT} |
|
||||
|-------------------------------------------------------|
|
||||
| Firmware & API: | Other: |
|
||||
| 1) [Klipper] | 6) [PrettyGCode] |
|
||||
| 2) [Moonraker] | 7) [Telegram Bot] |
|
||||
| | 8) $(obico_install_title) |
|
||||
| Klipper Webinterface: | 9) [OctoEverywhere] |
|
||||
| 3) [Mainsail] | 10) [Mobileraker] |
|
||||
| 4) [Fluidd] | |
|
||||
| | Webcam Streamer: |
|
||||
| Touchscreen GUI: | 11) [Crowsnest] |
|
||||
| 5) [KlipperScreen] | |
|
||||
"""
|
||||
)[1:]
|
||||
print(menu, end="")
|
||||
|
||||
def install_klipper(self):
|
||||
klipper_setup.run_klipper_setup(install=True)
|
||||
|
||||
def install_moonraker(self):
|
||||
moonraker_setup.run_moonraker_setup(install=True)
|
||||
|
||||
def install_mainsail(self):
|
||||
mainsail_setup.run_mainsail_installation()
|
||||
|
||||
def install_fluidd(self):
|
||||
print("install_fluidd")
|
||||
|
||||
def install_klipperscreen(self):
|
||||
print("install_klipperscreen")
|
||||
|
||||
def install_pretty_gcode(self):
|
||||
print("install_pretty_gcode")
|
||||
|
||||
def install_telegram_bot(self):
|
||||
print("install_telegram_bot")
|
||||
|
||||
def install_obico(self):
|
||||
print("install_obico")
|
||||
|
||||
def install_octoeverywhere(self):
|
||||
print("install_octoeverywhere")
|
||||
|
||||
def install_mobileraker(self):
|
||||
print("install_mobileraker")
|
||||
|
||||
def install_crowsnest(self):
|
||||
print("install_crowsnest")
|
||||
70
kiauh/core/menus/main_menu.py
Normal file
70
kiauh/core/menus/main_menu.py
Normal file
@@ -0,0 +1,70 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# ======================================================================= #
|
||||
# Copyright (C) 2020 - 2023 Dominik Willner <th33xitus@gmail.com> #
|
||||
# #
|
||||
# This file is part of KIAUH - Klipper Installation And Update Helper #
|
||||
# https://github.com/dw-0/kiauh #
|
||||
# #
|
||||
# This file may be distributed under the terms of the GNU GPLv3 license #
|
||||
# ======================================================================= #
|
||||
|
||||
import textwrap
|
||||
|
||||
from kiauh.core.menus import QUIT_FOOTER
|
||||
from kiauh.core.menus.advanced_menu import AdvancedMenu
|
||||
from kiauh.core.menus.base_menu import BaseMenu
|
||||
from kiauh.core.menus.install_menu import InstallMenu
|
||||
from kiauh.core.menus.remove_menu import RemoveMenu
|
||||
from kiauh.core.menus.settings_menu import SettingsMenu
|
||||
from kiauh.core.menus.update_menu import UpdateMenu
|
||||
from kiauh.utils.constants import COLOR_MAGENTA, COLOR_CYAN, RESET_FORMAT
|
||||
|
||||
|
||||
class MainMenu(BaseMenu):
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
header=True,
|
||||
options={
|
||||
0: None,
|
||||
1: InstallMenu,
|
||||
2: UpdateMenu,
|
||||
3: RemoveMenu,
|
||||
4: AdvancedMenu,
|
||||
5: None,
|
||||
6: SettingsMenu,
|
||||
},
|
||||
footer_type=QUIT_FOOTER,
|
||||
)
|
||||
|
||||
def print_menu(self):
|
||||
header = " [ Main Menu ] "
|
||||
footer1 = "KIAUH v6.0.0"
|
||||
footer2 = f"Changelog: {COLOR_MAGENTA}https://git.io/JnmlX{RESET_FORMAT}"
|
||||
color = COLOR_CYAN
|
||||
count = 62 - len(color) - len(RESET_FORMAT)
|
||||
menu = textwrap.dedent(
|
||||
f"""
|
||||
/=======================================================\\
|
||||
| {color}{header:~^{count}}{RESET_FORMAT} |
|
||||
|-------------------------------------------------------|
|
||||
| 0) [Log-Upload] | Klipper: <TODO> |
|
||||
| | Repo: <TODO> |
|
||||
| 1) [Install] | |
|
||||
| 2) [Update] | Moonraker: <TODO> |
|
||||
| 3) [Remove] | Repo: <TODO> |
|
||||
| 4) [Advanced] | |
|
||||
| 5) [Backup] | Mainsail: <TODO> |
|
||||
| | Fluidd: <TODO> |
|
||||
| 6) [Settings] | KlipperScreen: <TODO> |
|
||||
| | Mobileraker: <TODO> |
|
||||
| | |
|
||||
| | Crowsnest: <TODO> |
|
||||
| | Telegram Bot: <TODO> |
|
||||
| | Obico: <TODO> |
|
||||
| | OctoEverywhere: <TODO> |
|
||||
|-------------------------------------------------------|
|
||||
| {COLOR_CYAN}{footer1:^16}{RESET_FORMAT} | {footer2:^43} |
|
||||
"""
|
||||
)[1:]
|
||||
print(menu, end="")
|
||||
110
kiauh/core/menus/remove_menu.py
Normal file
110
kiauh/core/menus/remove_menu.py
Normal file
@@ -0,0 +1,110 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# ======================================================================= #
|
||||
# Copyright (C) 2020 - 2023 Dominik Willner <th33xitus@gmail.com> #
|
||||
# #
|
||||
# This file is part of KIAUH - Klipper Installation And Update Helper #
|
||||
# https://github.com/dw-0/kiauh #
|
||||
# #
|
||||
# This file may be distributed under the terms of the GNU GPLv3 license #
|
||||
# ======================================================================= #
|
||||
|
||||
import textwrap
|
||||
|
||||
from kiauh.core.menus import BACK_FOOTER
|
||||
from kiauh.core.menus.base_menu import BaseMenu
|
||||
from kiauh.modules.klipper import klipper_setup
|
||||
from kiauh.modules.mainsail import mainsail_setup
|
||||
from kiauh.modules.mainsail.menus.mainsail_remove_menu import MainsailRemoveMenu
|
||||
from kiauh.modules.moonraker import moonraker_setup
|
||||
from kiauh.utils.constants import COLOR_RED, RESET_FORMAT
|
||||
|
||||
|
||||
# noinspection PyMethodMayBeStatic
|
||||
class RemoveMenu(BaseMenu):
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
header=True,
|
||||
options={
|
||||
1: self.remove_klipper,
|
||||
2: self.remove_moonraker,
|
||||
3: MainsailRemoveMenu,
|
||||
5: self.remove_fluidd,
|
||||
6: self.remove_klipperscreen,
|
||||
7: self.remove_crowsnest,
|
||||
8: self.remove_mjpgstreamer,
|
||||
9: self.remove_pretty_gcode,
|
||||
10: self.remove_telegram_bot,
|
||||
11: self.remove_obico,
|
||||
12: self.remove_octoeverywhere,
|
||||
13: self.remove_mobileraker,
|
||||
14: self.remove_nginx,
|
||||
},
|
||||
footer_type=BACK_FOOTER,
|
||||
)
|
||||
|
||||
def print_menu(self):
|
||||
header = " [ Remove Menu ] "
|
||||
color = COLOR_RED
|
||||
count = 62 - len(color) - len(RESET_FORMAT)
|
||||
menu = textwrap.dedent(
|
||||
f"""
|
||||
/=======================================================\\
|
||||
| {color}{header:~^{count}}{RESET_FORMAT} |
|
||||
|-------------------------------------------------------|
|
||||
| INFO: Configurations and/or any backups will be kept! |
|
||||
|-------------------------------------------------------|
|
||||
| Firmware & API: | Webcam Streamer: |
|
||||
| 1) [Klipper] | 6) [Crowsnest] |
|
||||
| 2) [Moonraker] | 7) [MJPG-Streamer] |
|
||||
| | |
|
||||
| Klipper Webinterface: | Other: |
|
||||
| 3) [Mainsail] | 8) [PrettyGCode] |
|
||||
| 4) [Fluidd] | 9) [Telegram Bot] |
|
||||
| | 10) [Obico for Klipper] |
|
||||
| Touchscreen GUI: | 11) [OctoEverywhere] |
|
||||
| 5) [KlipperScreen] | 12) [Mobileraker] |
|
||||
| | 13) [NGINX] |
|
||||
| | |
|
||||
"""
|
||||
)[1:]
|
||||
print(menu, end="")
|
||||
|
||||
def remove_klipper(self):
|
||||
klipper_setup.run_klipper_setup(install=False)
|
||||
|
||||
def remove_moonraker(self):
|
||||
moonraker_setup.run_moonraker_setup(install=False)
|
||||
|
||||
def remove_fluidd(self):
|
||||
print("remove_fluidd")
|
||||
|
||||
def remove_fluidd_config(self):
|
||||
print("remove_fluidd_config")
|
||||
|
||||
def remove_klipperscreen(self):
|
||||
print("remove_klipperscreen")
|
||||
|
||||
def remove_crowsnest(self):
|
||||
print("remove_crowsnest")
|
||||
|
||||
def remove_mjpgstreamer(self):
|
||||
print("remove_mjpgstreamer")
|
||||
|
||||
def remove_pretty_gcode(self):
|
||||
print("remove_pretty_gcode")
|
||||
|
||||
def remove_telegram_bot(self):
|
||||
print("remove_telegram_bot")
|
||||
|
||||
def remove_obico(self):
|
||||
print("remove_obico")
|
||||
|
||||
def remove_octoeverywhere(self):
|
||||
print("remove_octoeverywhere")
|
||||
|
||||
def remove_mobileraker(self):
|
||||
print("remove_mobileraker")
|
||||
|
||||
def remove_nginx(self):
|
||||
print("remove_nginx")
|
||||
33
kiauh/core/menus/settings_menu.py
Normal file
33
kiauh/core/menus/settings_menu.py
Normal file
@@ -0,0 +1,33 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# ======================================================================= #
|
||||
# Copyright (C) 2020 - 2023 Dominik Willner <th33xitus@gmail.com> #
|
||||
# #
|
||||
# This file is part of KIAUH - Klipper Installation And Update Helper #
|
||||
# https://github.com/dw-0/kiauh #
|
||||
# #
|
||||
# This file may be distributed under the terms of the GNU GPLv3 license #
|
||||
# ======================================================================= #
|
||||
|
||||
from kiauh.core.menus.base_menu import BaseMenu
|
||||
|
||||
|
||||
# noinspection PyMethodMayBeStatic
|
||||
class SettingsMenu(BaseMenu):
|
||||
def __init__(self):
|
||||
super().__init__(header=True, options={})
|
||||
|
||||
def print_menu(self):
|
||||
print("self")
|
||||
|
||||
def execute_option_p(self):
|
||||
# Implement the functionality for Option P
|
||||
print("Executing Option P")
|
||||
|
||||
def execute_option_q(self):
|
||||
# Implement the functionality for Option Q
|
||||
print("Executing Option Q")
|
||||
|
||||
def execute_option_r(self):
|
||||
# Implement the functionality for Option R
|
||||
print("Executing Option R")
|
||||
115
kiauh/core/menus/update_menu.py
Normal file
115
kiauh/core/menus/update_menu.py
Normal file
@@ -0,0 +1,115 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# ======================================================================= #
|
||||
# Copyright (C) 2020 - 2023 Dominik Willner <th33xitus@gmail.com> #
|
||||
# #
|
||||
# This file is part of KIAUH - Klipper Installation And Update Helper #
|
||||
# https://github.com/dw-0/kiauh #
|
||||
# #
|
||||
# This file may be distributed under the terms of the GNU GPLv3 license #
|
||||
# ======================================================================= #
|
||||
|
||||
import textwrap
|
||||
|
||||
from kiauh.core.menus import BACK_FOOTER
|
||||
from kiauh.core.menus.base_menu import BaseMenu
|
||||
from kiauh.modules.klipper.klipper_setup import update_klipper
|
||||
from kiauh.utils.constants import COLOR_GREEN, RESET_FORMAT
|
||||
|
||||
|
||||
# noinspection PyMethodMayBeStatic
|
||||
class UpdateMenu(BaseMenu):
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
header=True,
|
||||
options={
|
||||
0: self.update_all,
|
||||
1: self.update_klipper,
|
||||
2: self.update_moonraker,
|
||||
3: self.update_mainsail,
|
||||
4: self.update_fluidd,
|
||||
5: self.update_klipperscreen,
|
||||
6: self.update_pgc_for_klipper,
|
||||
7: self.update_telegram_bot,
|
||||
8: self.update_moonraker_obico,
|
||||
9: self.update_octoeverywhere,
|
||||
10: self.update_mobileraker,
|
||||
11: self.update_crowsnest,
|
||||
12: self.upgrade_system_packages,
|
||||
},
|
||||
footer_type=BACK_FOOTER,
|
||||
)
|
||||
|
||||
def print_menu(self):
|
||||
header = " [ Update Menu ] "
|
||||
color = COLOR_GREEN
|
||||
count = 62 - len(color) - len(RESET_FORMAT)
|
||||
menu = textwrap.dedent(
|
||||
f"""
|
||||
/=======================================================\\
|
||||
| {color}{header:~^{count}}{RESET_FORMAT} |
|
||||
|-------------------------------------------------------|
|
||||
| 0) [Update all] | | |
|
||||
| | Current: | Latest: |
|
||||
| Klipper & API: |--------------|--------------|
|
||||
| 1) [Klipper] | | |
|
||||
| 2) [Moonraker] | | |
|
||||
| | | |
|
||||
| Klipper Webinterface: |--------------|--------------|
|
||||
| 3) [Mainsail] | | |
|
||||
| 4) [Fluidd] | | |
|
||||
| | | |
|
||||
| Touchscreen GUI: |--------------|--------------|
|
||||
| 5) [KlipperScreen] | | |
|
||||
| | | |
|
||||
| Other: |--------------|--------------|
|
||||
| 6) [PrettyGCode] | | |
|
||||
| 7) [Telegram Bot] | | |
|
||||
| 8) [Obico for Klipper] | | |
|
||||
| 9) [OctoEverywhere] | | |
|
||||
| 10) [Mobileraker] | | |
|
||||
| 11) [Crowsnest] | | |
|
||||
| |-----------------------------|
|
||||
| 12) [System] | | |
|
||||
"""
|
||||
)[1:]
|
||||
print(menu, end="")
|
||||
|
||||
def update_all(self):
|
||||
print("update_all")
|
||||
|
||||
def update_klipper(self):
|
||||
update_klipper()
|
||||
|
||||
def update_moonraker(self):
|
||||
print("update_moonraker")
|
||||
|
||||
def update_mainsail(self):
|
||||
print("update_mainsail")
|
||||
|
||||
def update_fluidd(self):
|
||||
print("update_fluidd")
|
||||
|
||||
def update_klipperscreen(self):
|
||||
print("update_klipperscreen")
|
||||
|
||||
def update_pgc_for_klipper(self):
|
||||
print("update_pgc_for_klipper")
|
||||
|
||||
def update_telegram_bot(self):
|
||||
print("update_telegram_bot")
|
||||
|
||||
def update_moonraker_obico(self):
|
||||
print("update_moonraker_obico")
|
||||
|
||||
def update_octoeverywhere(self):
|
||||
print("update_octoeverywhere")
|
||||
|
||||
def update_mobileraker(self):
|
||||
print("update_mobileraker")
|
||||
|
||||
def update_crowsnest(self):
|
||||
print("update_crowsnest")
|
||||
|
||||
def upgrade_system_packages(self):
|
||||
print("upgrade_system_packages")
|
||||
0
kiauh/core/repo_manager/__init__.py
Normal file
0
kiauh/core/repo_manager/__init__.py
Normal file
127
kiauh/core/repo_manager/repo_manager.py
Normal file
127
kiauh/core/repo_manager/repo_manager.py
Normal file
@@ -0,0 +1,127 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# ======================================================================= #
|
||||
# Copyright (C) 2020 - 2023 Dominik Willner <th33xitus@gmail.com> #
|
||||
# #
|
||||
# This file is part of KIAUH - Klipper Installation And Update Helper #
|
||||
# https://github.com/dw-0/kiauh #
|
||||
# #
|
||||
# This file may be distributed under the terms of the GNU GPLv3 license #
|
||||
# ======================================================================= #
|
||||
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
|
||||
from kiauh.utils.input_utils import get_confirm
|
||||
from kiauh.utils.logger import Logger
|
||||
|
||||
|
||||
# noinspection PyMethodMayBeStatic
|
||||
class RepoManager:
|
||||
def __init__(
|
||||
self,
|
||||
repo: str,
|
||||
target_dir: str,
|
||||
branch: str = None,
|
||||
):
|
||||
self._repo = repo
|
||||
self._branch = branch if branch is not None else "master"
|
||||
self._method = self._get_method()
|
||||
self._target_dir = target_dir
|
||||
|
||||
@property
|
||||
def repo(self) -> str:
|
||||
return self._repo
|
||||
|
||||
@repo.setter
|
||||
def repo(self, value) -> None:
|
||||
self._repo = value
|
||||
|
||||
@property
|
||||
def branch(self) -> str:
|
||||
return self._branch
|
||||
|
||||
@branch.setter
|
||||
def branch(self, value) -> None:
|
||||
self._branch = value
|
||||
|
||||
@property
|
||||
def method(self) -> str:
|
||||
return self._method
|
||||
|
||||
@method.setter
|
||||
def method(self, value) -> None:
|
||||
self._method = value
|
||||
|
||||
@property
|
||||
def target_dir(self) -> str:
|
||||
return self._target_dir
|
||||
|
||||
@target_dir.setter
|
||||
def target_dir(self, value) -> None:
|
||||
self._target_dir = value
|
||||
|
||||
def clone_repo(self):
|
||||
log = f"Cloning repository from '{self.repo}' with method '{self.method}'"
|
||||
Logger.print_status(log)
|
||||
try:
|
||||
if os.path.exists(self.target_dir):
|
||||
question = f"'{self.target_dir}' already exists. Overwrite?"
|
||||
if not get_confirm(question, default_choice=False):
|
||||
Logger.print_info("Skipping re-clone of repository.")
|
||||
return
|
||||
shutil.rmtree(self.target_dir)
|
||||
|
||||
self._clone()
|
||||
self._checkout()
|
||||
except subprocess.CalledProcessError:
|
||||
log = "An unexpected error occured during cloning of the repository."
|
||||
Logger.print_error(log)
|
||||
return
|
||||
except OSError as e:
|
||||
Logger.print_error(f"Error removing existing repository: {e.strerror}")
|
||||
return
|
||||
|
||||
def pull_repo(self) -> None:
|
||||
Logger.print_status(f"Updating repository '{self.repo}' ...")
|
||||
try:
|
||||
self._pull()
|
||||
except subprocess.CalledProcessError:
|
||||
log = "An unexpected error occured during updating the repository."
|
||||
Logger.print_error(log)
|
||||
return
|
||||
|
||||
def _clone(self):
|
||||
try:
|
||||
command = ["git", "clone", self.repo, self.target_dir]
|
||||
subprocess.run(command, check=True)
|
||||
|
||||
Logger.print_ok("Clone successfull!")
|
||||
except subprocess.CalledProcessError as e:
|
||||
log = f"Error cloning repository {self.repo}: {e.stderr.decode()}"
|
||||
Logger.print_error(log)
|
||||
raise
|
||||
|
||||
def _checkout(self):
|
||||
try:
|
||||
command = ["git", "checkout", f"{self.branch}"]
|
||||
subprocess.run(command, cwd=self.target_dir, check=True)
|
||||
|
||||
Logger.print_ok("Checkout successfull!")
|
||||
except subprocess.CalledProcessError as e:
|
||||
log = f"Error checking out branch {self.branch}: {e.stderr.decode()}"
|
||||
Logger.print_error(log)
|
||||
raise
|
||||
|
||||
def _pull(self) -> None:
|
||||
try:
|
||||
command = ["git", "pull"]
|
||||
subprocess.run(command, cwd=self.target_dir, check=True)
|
||||
except subprocess.CalledProcessError as e:
|
||||
log = f"Error on git pull: {e.stderr.decode()}"
|
||||
Logger.print_error(log)
|
||||
raise
|
||||
|
||||
def _get_method(self) -> str:
|
||||
return "ssh" if self.repo.startswith("git") else "https"
|
||||
20
kiauh/main.py
Normal file
20
kiauh/main.py
Normal file
@@ -0,0 +1,20 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# ======================================================================= #
|
||||
# Copyright (C) 2020 - 2023 Dominik Willner <th33xitus@gmail.com> #
|
||||
# #
|
||||
# This file is part of KIAUH - Klipper Installation And Update Helper #
|
||||
# https://github.com/dw-0/kiauh #
|
||||
# #
|
||||
# This file may be distributed under the terms of the GNU GPLv3 license #
|
||||
# ======================================================================= #
|
||||
|
||||
from kiauh.core.menus.main_menu import MainMenu
|
||||
from kiauh.utils.logger import Logger
|
||||
|
||||
|
||||
def main():
|
||||
try:
|
||||
MainMenu().start()
|
||||
except KeyboardInterrupt:
|
||||
Logger.print_ok("\nHappy printing!\n", prefix=False)
|
||||
0
kiauh/modules/__init__.py
Normal file
0
kiauh/modules/__init__.py
Normal file
22
kiauh/modules/klipper/__init__.py
Normal file
22
kiauh/modules/klipper/__init__.py
Normal file
@@ -0,0 +1,22 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# ======================================================================= #
|
||||
# Copyright (C) 2020 - 2023 Dominik Willner <th33xitus@gmail.com> #
|
||||
# #
|
||||
# This file is part of KIAUH - Klipper Installation And Update Helper #
|
||||
# https://github.com/dw-0/kiauh #
|
||||
# #
|
||||
# This file may be distributed under the terms of the GNU GPLv3 license #
|
||||
# ======================================================================= #
|
||||
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
MODULE_PATH = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
KLIPPER_DIR = f"{Path.home()}/klipper"
|
||||
KLIPPER_ENV_DIR = f"{Path.home()}/klippy-env"
|
||||
KLIPPER_REQUIREMENTS_TXT = f"{KLIPPER_DIR}/scripts/klippy-requirements.txt"
|
||||
DEFAULT_KLIPPER_REPO_URL = "https://github.com/Klipper3D/klipper"
|
||||
|
||||
EXIT_KLIPPER_SETUP = "Exiting Klipper setup ..."
|
||||
171
kiauh/modules/klipper/klipper.py
Normal file
171
kiauh/modules/klipper/klipper.py
Normal file
@@ -0,0 +1,171 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# ======================================================================= #
|
||||
# Copyright (C) 2020 - 2023 Dominik Willner <th33xitus@gmail.com> #
|
||||
# #
|
||||
# This file is part of KIAUH - Klipper Installation And Update Helper #
|
||||
# https://github.com/dw-0/kiauh #
|
||||
# #
|
||||
# This file may be distributed under the terms of the GNU GPLv3 license #
|
||||
# ======================================================================= #
|
||||
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
from typing import List
|
||||
|
||||
from kiauh.core.instance_manager.base_instance import BaseInstance
|
||||
from kiauh.modules.klipper import KLIPPER_DIR, KLIPPER_ENV_DIR
|
||||
from kiauh.utils.constants import SYSTEMD
|
||||
from kiauh.utils.logger import Logger
|
||||
|
||||
|
||||
# noinspection PyMethodMayBeStatic
|
||||
class Klipper(BaseInstance):
|
||||
@classmethod
|
||||
def blacklist(cls) -> List[str]:
|
||||
return ["None", "mcu"]
|
||||
|
||||
def __init__(self, suffix: str = None):
|
||||
super().__init__(instance_type=self, suffix=suffix)
|
||||
self.klipper_dir = KLIPPER_DIR
|
||||
self.env_dir = KLIPPER_ENV_DIR
|
||||
self._cfg_file = self._get_cfg()
|
||||
self._log = f"{self.log_dir}/klippy.log"
|
||||
self._serial = f"{self.comms_dir}/klippy.serial"
|
||||
self._uds = f"{self.comms_dir}/klippy.sock"
|
||||
|
||||
@property
|
||||
def cfg_file(self) -> str:
|
||||
return self._cfg_file
|
||||
|
||||
@property
|
||||
def log(self) -> str:
|
||||
return self._log
|
||||
|
||||
@property
|
||||
def serial(self) -> str:
|
||||
return self._serial
|
||||
|
||||
@property
|
||||
def uds(self) -> str:
|
||||
return self._uds
|
||||
|
||||
def create(self) -> None:
|
||||
Logger.print_status("Creating new Klipper Instance ...")
|
||||
module_path = os.path.dirname(os.path.abspath(__file__))
|
||||
service_template_path = os.path.join(module_path, "res", "klipper.service")
|
||||
env_template_file_path = os.path.join(module_path, "res", "klipper.env")
|
||||
service_file_name = self.get_service_file_name(extension=True)
|
||||
service_file_target = f"{SYSTEMD}/{service_file_name}"
|
||||
env_file_target = os.path.abspath(f"{self.sysd_dir}/klipper.env")
|
||||
|
||||
try:
|
||||
self.create_folders()
|
||||
self.write_service_file(
|
||||
service_template_path, service_file_target, env_file_target
|
||||
)
|
||||
self.write_env_file(env_template_file_path, env_file_target)
|
||||
|
||||
except subprocess.CalledProcessError as e:
|
||||
Logger.print_error(
|
||||
f"Error creating service file {service_file_target}: {e}"
|
||||
)
|
||||
raise
|
||||
except OSError as e:
|
||||
Logger.print_error(f"Error creating env file {env_file_target}: {e}")
|
||||
raise
|
||||
|
||||
def delete(self, del_remnants: bool) -> None:
|
||||
service_file = self.get_service_file_name(extension=True)
|
||||
service_file_path = self.get_service_file_path()
|
||||
|
||||
Logger.print_status(f"Deleting Klipper Instance: {service_file}")
|
||||
|
||||
try:
|
||||
command = ["sudo", "rm", "-f", service_file_path]
|
||||
subprocess.run(command, check=True)
|
||||
Logger.print_ok(f"Service file deleted: {service_file_path}")
|
||||
except subprocess.CalledProcessError as e:
|
||||
Logger.print_error(f"Error deleting service file: {e}")
|
||||
raise
|
||||
|
||||
if del_remnants:
|
||||
self._delete_klipper_remnants()
|
||||
|
||||
def write_service_file(
|
||||
self, service_template_path: str, service_file_target: str, env_file_target: str
|
||||
):
|
||||
service_content = self._prep_service_file(
|
||||
service_template_path, env_file_target
|
||||
)
|
||||
command = ["sudo", "tee", service_file_target]
|
||||
subprocess.run(
|
||||
command,
|
||||
input=service_content.encode(),
|
||||
stdout=subprocess.DEVNULL,
|
||||
check=True,
|
||||
)
|
||||
Logger.print_ok(f"Service file created: {service_file_target}")
|
||||
|
||||
def write_env_file(self, env_template_file_path: str, env_file_target: str):
|
||||
env_file_content = self._prep_env_file(env_template_file_path)
|
||||
with open(env_file_target, "w") as env_file:
|
||||
env_file.write(env_file_content)
|
||||
Logger.print_ok(f"Env file created: {env_file_target}")
|
||||
|
||||
def _delete_klipper_remnants(self) -> None:
|
||||
try:
|
||||
Logger.print_status(f"Delete {self.klipper_dir} ...")
|
||||
shutil.rmtree(Path(self.klipper_dir))
|
||||
Logger.print_status(f"Delete {self.env_dir} ...")
|
||||
shutil.rmtree(Path(self.env_dir))
|
||||
except FileNotFoundError:
|
||||
Logger.print_status("Cannot delete Klipper directories. Not found.")
|
||||
except PermissionError as e:
|
||||
Logger.print_error(f"Error deleting Klipper directories: {e}")
|
||||
raise
|
||||
|
||||
Logger.print_ok("Directories successfully deleted.")
|
||||
|
||||
def _prep_service_file(self, service_template_path, env_file_path):
|
||||
try:
|
||||
with open(service_template_path, "r") as template_file:
|
||||
template_content = template_file.read()
|
||||
except FileNotFoundError:
|
||||
Logger.print_error(
|
||||
f"Unable to open {service_template_path} - File not found"
|
||||
)
|
||||
raise
|
||||
service_content = template_content.replace("%USER%", self.user)
|
||||
service_content = service_content.replace("%KLIPPER_DIR%", self.klipper_dir)
|
||||
service_content = service_content.replace("%ENV%", self.env_dir)
|
||||
service_content = service_content.replace("%ENV_FILE%", env_file_path)
|
||||
return service_content
|
||||
|
||||
def _prep_env_file(self, env_template_file_path):
|
||||
try:
|
||||
with open(env_template_file_path, "r") as env_file:
|
||||
env_template_file_content = env_file.read()
|
||||
except FileNotFoundError:
|
||||
Logger.print_error(
|
||||
f"Unable to open {env_template_file_path} - File not found"
|
||||
)
|
||||
raise
|
||||
env_file_content = env_template_file_content.replace(
|
||||
"%KLIPPER_DIR%", self.klipper_dir
|
||||
)
|
||||
env_file_content = env_file_content.replace(
|
||||
"%CFG%", f"{self.cfg_dir}/printer.cfg"
|
||||
)
|
||||
env_file_content = env_file_content.replace("%SERIAL%", self._serial)
|
||||
env_file_content = env_file_content.replace("%LOG%", self._log)
|
||||
env_file_content = env_file_content.replace("%UDS%", self._uds)
|
||||
return env_file_content
|
||||
|
||||
def _get_cfg(self):
|
||||
cfg_file_loc = f"{self.cfg_dir}/printer.cfg"
|
||||
if Path(cfg_file_loc).is_file():
|
||||
return cfg_file_loc
|
||||
return None
|
||||
136
kiauh/modules/klipper/klipper_dialogs.py
Normal file
136
kiauh/modules/klipper/klipper_dialogs.py
Normal file
@@ -0,0 +1,136 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# ======================================================================= #
|
||||
# Copyright (C) 2020 - 2023 Dominik Willner <th33xitus@gmail.com> #
|
||||
# #
|
||||
# This file is part of KIAUH - Klipper Installation And Update Helper #
|
||||
# https://github.com/dw-0/kiauh #
|
||||
# #
|
||||
# This file may be distributed under the terms of the GNU GPLv3 license #
|
||||
# ======================================================================= #
|
||||
|
||||
import textwrap
|
||||
from typing import List
|
||||
|
||||
from kiauh.core.instance_manager.base_instance import BaseInstance
|
||||
from kiauh.core.menus.base_menu import print_back_footer
|
||||
from kiauh.utils.constants import COLOR_GREEN, RESET_FORMAT, COLOR_YELLOW, COLOR_CYAN
|
||||
|
||||
|
||||
def print_instance_overview(
|
||||
instances: List[BaseInstance], show_index=False, show_select_all=False
|
||||
):
|
||||
headline = f"{COLOR_GREEN}The following Klipper instances were found:{RESET_FORMAT}"
|
||||
dialog = textwrap.dedent(
|
||||
f"""
|
||||
/=======================================================\\
|
||||
|{headline:^64}|
|
||||
|-------------------------------------------------------|
|
||||
"""
|
||||
)[1:]
|
||||
|
||||
if show_select_all:
|
||||
select_all = f"{COLOR_YELLOW}a) Select all{RESET_FORMAT}"
|
||||
dialog += f"| {select_all:<63}|\n"
|
||||
dialog += "| |\n"
|
||||
|
||||
for i, s in enumerate(instances):
|
||||
line = f"{COLOR_CYAN}{f'{i})' if show_index else '●'} {s.get_service_file_name()}{RESET_FORMAT}"
|
||||
dialog += f"| {line:<63}|\n"
|
||||
|
||||
print(dialog, end="")
|
||||
print_back_footer()
|
||||
|
||||
|
||||
def print_select_instance_count_dialog():
|
||||
line1 = f"{COLOR_YELLOW}WARNING:{RESET_FORMAT}"
|
||||
line2 = f"{COLOR_YELLOW}Setting up too many instances may crash your system.{RESET_FORMAT}"
|
||||
dialog = textwrap.dedent(
|
||||
f"""
|
||||
/=======================================================\\
|
||||
| Please select the number of Klipper instances to set |
|
||||
| up. The number of Klipper instances will determine |
|
||||
| the amount of printers you can run from this host. |
|
||||
| |
|
||||
| {line1:<63}|
|
||||
| {line2:<63}|
|
||||
"""
|
||||
)[1:]
|
||||
|
||||
print(dialog, end="")
|
||||
print_back_footer()
|
||||
|
||||
|
||||
def print_select_custom_name_dialog():
|
||||
line1 = f"{COLOR_YELLOW}INFO:{RESET_FORMAT}"
|
||||
line2 = f"{COLOR_YELLOW}Only alphanumeric characters are allowed!{RESET_FORMAT}"
|
||||
dialog = textwrap.dedent(
|
||||
f"""
|
||||
/=======================================================\\
|
||||
| You can now assign a custom name to each instance. |
|
||||
| If skipped, each instance will get an index assigned |
|
||||
| in ascending order, starting at index '1'. |
|
||||
| |
|
||||
| {line1:<63}|
|
||||
| {line2:<63}|
|
||||
"""
|
||||
)[1:]
|
||||
|
||||
print(dialog, end="")
|
||||
print_back_footer()
|
||||
|
||||
|
||||
def print_missing_usergroup_dialog(missing_groups) -> None:
|
||||
line1 = f"{COLOR_YELLOW}WARNING: Your current user is not in group:{RESET_FORMAT}"
|
||||
line2 = f"{COLOR_CYAN}● tty{RESET_FORMAT}"
|
||||
line3 = f"{COLOR_CYAN}● dialout{RESET_FORMAT}"
|
||||
line4 = f"{COLOR_YELLOW}INFO:{RESET_FORMAT}"
|
||||
line5 = f"{COLOR_YELLOW}Relog required for group assignments to take effect!{RESET_FORMAT}"
|
||||
|
||||
dialog = textwrap.dedent(
|
||||
f"""
|
||||
/=======================================================\\
|
||||
| {line1:<63}|
|
||||
"""
|
||||
)[1:]
|
||||
|
||||
if "tty" in missing_groups:
|
||||
dialog += f"| {line2:<63}|\n"
|
||||
if "dialout" in missing_groups:
|
||||
dialog += f"| {line3:<63}|\n"
|
||||
|
||||
dialog += textwrap.dedent(
|
||||
f"""
|
||||
| |
|
||||
| It is possible that you won't be able to successfully |
|
||||
| connect and/or flash the controller board without |
|
||||
| your user being a member of that group. |
|
||||
| If you want to add the current user to the group(s) |
|
||||
| listed above, answer with 'Y'. Else skip with 'n'. |
|
||||
| |
|
||||
| {line4:<63}|
|
||||
| {line5:<63}|
|
||||
\\=======================================================/
|
||||
"""
|
||||
)[1:]
|
||||
|
||||
print(dialog, end="")
|
||||
|
||||
|
||||
def print_update_warn_dialog() -> None:
|
||||
line1 = f"{COLOR_YELLOW}WARNING:{RESET_FORMAT}"
|
||||
line2 = f"{COLOR_YELLOW}Do NOT continue if there are ongoing prints running!{RESET_FORMAT}"
|
||||
line3 = f"{COLOR_YELLOW}All Klipper instances will be restarted during the {RESET_FORMAT}"
|
||||
line4 = f"{COLOR_YELLOW}update process and ongoing prints WILL FAIL.{RESET_FORMAT}"
|
||||
dialog = textwrap.dedent(
|
||||
f"""
|
||||
/=======================================================\\
|
||||
| {line1:<63}|
|
||||
| {line2:<63}|
|
||||
| {line3:<63}|
|
||||
| {line4:<63}|
|
||||
\\=======================================================/
|
||||
"""
|
||||
)[1:]
|
||||
|
||||
print(dialog, end="")
|
||||
289
kiauh/modules/klipper/klipper_setup.py
Normal file
289
kiauh/modules/klipper/klipper_setup.py
Normal file
@@ -0,0 +1,289 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# ======================================================================= #
|
||||
# Copyright (C) 2020 - 2023 Dominik Willner <th33xitus@gmail.com> #
|
||||
# #
|
||||
# This file is part of KIAUH - Klipper Installation And Update Helper #
|
||||
# https://github.com/dw-0/kiauh #
|
||||
# #
|
||||
# This file may be distributed under the terms of the GNU GPLv3 license #
|
||||
# ======================================================================= #
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
from typing import List, Union
|
||||
|
||||
from kiauh import KIAUH_CFG
|
||||
from kiauh.core.backup_manager.backup_manager import BackupManager
|
||||
from kiauh.core.config_manager.config_manager import ConfigManager
|
||||
from kiauh.core.instance_manager.instance_manager import InstanceManager
|
||||
from kiauh.modules.klipper import (
|
||||
EXIT_KLIPPER_SETUP,
|
||||
DEFAULT_KLIPPER_REPO_URL,
|
||||
KLIPPER_DIR,
|
||||
KLIPPER_ENV_DIR,
|
||||
KLIPPER_REQUIREMENTS_TXT,
|
||||
)
|
||||
from kiauh.modules.klipper.klipper import Klipper
|
||||
from kiauh.modules.klipper.klipper_dialogs import (
|
||||
print_instance_overview,
|
||||
print_select_instance_count_dialog,
|
||||
print_update_warn_dialog,
|
||||
)
|
||||
from kiauh.modules.klipper.klipper_utils import (
|
||||
handle_convert_single_to_multi_instance_names,
|
||||
handle_new_multi_instance_names,
|
||||
handle_existing_multi_instance_names,
|
||||
handle_disruptive_system_packages,
|
||||
check_user_groups,
|
||||
handle_single_to_multi_conversion,
|
||||
create_example_printer_cfg,
|
||||
)
|
||||
from kiauh.core.repo_manager.repo_manager import RepoManager
|
||||
from kiauh.utils.input_utils import (
|
||||
get_confirm,
|
||||
get_number_input,
|
||||
get_selection_input,
|
||||
)
|
||||
from kiauh.utils.logger import Logger
|
||||
from kiauh.utils.system_utils import (
|
||||
parse_packages_from_file,
|
||||
create_python_venv,
|
||||
install_python_requirements,
|
||||
update_system_package_lists,
|
||||
install_system_packages,
|
||||
)
|
||||
|
||||
|
||||
def run_klipper_setup(install: bool) -> None:
|
||||
instance_manager = InstanceManager(Klipper)
|
||||
instance_list = instance_manager.instances
|
||||
instances_installed = len(instance_list)
|
||||
|
||||
is_klipper_installed = instances_installed > 0
|
||||
if not install and not is_klipper_installed:
|
||||
Logger.print_warn("Klipper not installed!")
|
||||
return
|
||||
|
||||
if install:
|
||||
add_additional = handle_existing_instances(instance_list)
|
||||
if is_klipper_installed and not add_additional:
|
||||
Logger.print_status(EXIT_KLIPPER_SETUP)
|
||||
return
|
||||
|
||||
install_klipper(instance_manager, instance_list)
|
||||
|
||||
if not install:
|
||||
if instances_installed == 1:
|
||||
remove_single_instance(instance_manager, instance_list)
|
||||
else:
|
||||
remove_multi_instance(instance_manager, instance_list)
|
||||
|
||||
|
||||
def handle_existing_instances(instance_list: List[Klipper]) -> bool:
|
||||
instance_count = len(instance_list)
|
||||
|
||||
if instance_count > 0:
|
||||
print_instance_overview(instance_list)
|
||||
if not get_confirm("Add new instances?", allow_go_back=True):
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def install_klipper(
|
||||
instance_manager: InstanceManager, instance_list: List[Klipper]
|
||||
) -> None:
|
||||
print_select_instance_count_dialog()
|
||||
question = f"Number of{' additional' if len(instance_list) > 0 else ''} Klipper instances to set up"
|
||||
install_count = get_number_input(question, 1, default=1, allow_go_back=True)
|
||||
if install_count is None:
|
||||
Logger.print_status(EXIT_KLIPPER_SETUP)
|
||||
return
|
||||
|
||||
instance_names = set_instance_suffix(instance_list, install_count)
|
||||
if instance_names is None:
|
||||
Logger.print_status(EXIT_KLIPPER_SETUP)
|
||||
return
|
||||
|
||||
create_example_cfg = get_confirm("Create example printer.cfg?")
|
||||
|
||||
if len(instance_list) < 1:
|
||||
setup_klipper_prerequesites()
|
||||
|
||||
convert_single_to_multi = (
|
||||
len(instance_list) == 1
|
||||
and instance_list[0].suffix is None
|
||||
and install_count >= 1
|
||||
)
|
||||
|
||||
for name in instance_names:
|
||||
if convert_single_to_multi:
|
||||
current_instance = handle_single_to_multi_conversion(instance_manager, name)
|
||||
convert_single_to_multi = False
|
||||
else:
|
||||
current_instance = Klipper(suffix=name)
|
||||
|
||||
instance_manager.current_instance = current_instance
|
||||
instance_manager.create_instance()
|
||||
instance_manager.enable_instance()
|
||||
|
||||
if create_example_cfg:
|
||||
create_example_printer_cfg(current_instance)
|
||||
|
||||
instance_manager.start_instance()
|
||||
|
||||
instance_manager.reload_daemon()
|
||||
|
||||
# step 4: check/handle conflicting packages/services
|
||||
handle_disruptive_system_packages()
|
||||
|
||||
# step 5: check for required group membership
|
||||
check_user_groups()
|
||||
|
||||
|
||||
def setup_klipper_prerequesites() -> None:
|
||||
cm = ConfigManager(cfg_file=KIAUH_CFG)
|
||||
cm.read_config()
|
||||
|
||||
repo = str(cm.get_value("klipper", "repository_url") or DEFAULT_KLIPPER_REPO_URL)
|
||||
branch = str(cm.get_value("klipper", "branch") or "master")
|
||||
|
||||
repo_manager = RepoManager(
|
||||
repo=repo,
|
||||
branch=branch,
|
||||
target_dir=KLIPPER_DIR,
|
||||
)
|
||||
repo_manager.clone_repo()
|
||||
|
||||
# install klipper dependencies and create python virtualenv
|
||||
install_klipper_packages(Path(KLIPPER_DIR))
|
||||
create_python_venv(Path(KLIPPER_ENV_DIR))
|
||||
klipper_py_req = Path(KLIPPER_REQUIREMENTS_TXT)
|
||||
install_python_requirements(Path(KLIPPER_ENV_DIR), klipper_py_req)
|
||||
|
||||
|
||||
def install_klipper_packages(klipper_dir: Path) -> None:
|
||||
script = Path(f"{klipper_dir}/scripts/install-debian.sh")
|
||||
packages = parse_packages_from_file(script)
|
||||
packages = [pkg.replace("python-dev", "python3-dev") for pkg in packages]
|
||||
# Add dfu-util for octopi-images
|
||||
packages.append("dfu-util")
|
||||
# Add dbus requirement for DietPi distro
|
||||
if os.path.exists("/boot/dietpi/.version"):
|
||||
packages.append("dbus")
|
||||
|
||||
update_system_package_lists(silent=False)
|
||||
install_system_packages(packages)
|
||||
|
||||
|
||||
def set_instance_suffix(
|
||||
instance_list: List[Klipper], install_count: int
|
||||
) -> List[Union[str, None]]:
|
||||
instance_count = len(instance_list)
|
||||
|
||||
# new single instance install
|
||||
if instance_count == 0 and install_count == 1:
|
||||
return [None]
|
||||
|
||||
# convert single instance install to multi install
|
||||
elif instance_count == 1 and install_count >= 1 and instance_list[0].suffix is None:
|
||||
return handle_convert_single_to_multi_instance_names(install_count)
|
||||
|
||||
# new multi instance install
|
||||
elif instance_count == 0 and install_count > 1:
|
||||
return handle_new_multi_instance_names(instance_count, install_count)
|
||||
|
||||
# existing multi instance install
|
||||
elif instance_count > 1:
|
||||
return handle_existing_multi_instance_names(
|
||||
instance_count, install_count, instance_list
|
||||
)
|
||||
|
||||
|
||||
def remove_single_instance(
|
||||
instance_manager: InstanceManager, instance_list: List[Klipper]
|
||||
) -> None:
|
||||
question = f"Delete {KLIPPER_DIR} and {KLIPPER_ENV_DIR}?"
|
||||
del_remnants = get_confirm(question, allow_go_back=True)
|
||||
if del_remnants is None:
|
||||
Logger.print_status("Exiting Klipper Uninstaller ...")
|
||||
return
|
||||
|
||||
try:
|
||||
instance_manager.current_instance = instance_list[0]
|
||||
instance_manager.stop_instance()
|
||||
instance_manager.disable_instance()
|
||||
instance_manager.delete_instance(del_remnants=del_remnants)
|
||||
instance_manager.reload_daemon()
|
||||
except (OSError, subprocess.CalledProcessError):
|
||||
Logger.print_error("Removing instance failed!")
|
||||
return
|
||||
|
||||
|
||||
def remove_multi_instance(
|
||||
instance_manager: InstanceManager, instance_list: List[Klipper]
|
||||
) -> None:
|
||||
print_instance_overview(instance_list, show_index=True, show_select_all=True)
|
||||
|
||||
options = [str(i) for i in range(len(instance_list))]
|
||||
options.extend(["a", "A", "b", "B"])
|
||||
|
||||
selection = get_selection_input("Select Klipper instance to remove", options)
|
||||
|
||||
if selection == "b".lower():
|
||||
return
|
||||
elif selection == "a".lower():
|
||||
question = f"Delete {KLIPPER_DIR} and {KLIPPER_ENV_DIR}?"
|
||||
del_remnants = get_confirm(question, allow_go_back=True)
|
||||
if del_remnants is None:
|
||||
Logger.print_status("Exiting Klipper Uninstaller ...")
|
||||
return
|
||||
|
||||
Logger.print_status("Removing all Klipper instances ...")
|
||||
for instance in instance_list:
|
||||
instance_manager.current_instance = instance
|
||||
instance_manager.stop_instance()
|
||||
instance_manager.disable_instance()
|
||||
instance_manager.delete_instance(del_remnants=del_remnants)
|
||||
else:
|
||||
instance = instance_list[int(selection)]
|
||||
log = f"Removing Klipper instance: {instance.get_service_file_name()}"
|
||||
Logger.print_status(log)
|
||||
instance_manager.current_instance = instance
|
||||
instance_manager.stop_instance()
|
||||
instance_manager.disable_instance()
|
||||
instance_manager.delete_instance(del_remnants=False)
|
||||
|
||||
instance_manager.reload_daemon()
|
||||
|
||||
|
||||
def update_klipper() -> None:
|
||||
print_update_warn_dialog()
|
||||
if not get_confirm("Update Klipper now?"):
|
||||
return
|
||||
|
||||
cm = ConfigManager(cfg_file=KIAUH_CFG)
|
||||
cm.read_config()
|
||||
|
||||
if cm.get_value("kiauh", "backup_before_update"):
|
||||
backup_manager = BackupManager(source=KLIPPER_DIR, backup_name="klipper")
|
||||
backup_manager.backup()
|
||||
backup_manager.backup_name = "klippy-env"
|
||||
backup_manager.source = KLIPPER_ENV_DIR
|
||||
backup_manager.backup()
|
||||
|
||||
instance_manager = InstanceManager(Klipper)
|
||||
instance_manager.stop_all_instance()
|
||||
|
||||
repo = str(cm.get_value("klipper", "repository_url") or DEFAULT_KLIPPER_REPO_URL)
|
||||
branch = str(cm.get_value("klipper", "branch") or "master")
|
||||
|
||||
repo_manager = RepoManager(
|
||||
repo=repo,
|
||||
branch=branch,
|
||||
target_dir=KLIPPER_DIR,
|
||||
)
|
||||
repo_manager.pull_repo()
|
||||
instance_manager.start_all_instance()
|
||||
215
kiauh/modules/klipper/klipper_utils.py
Normal file
215
kiauh/modules/klipper/klipper_utils.py
Normal file
@@ -0,0 +1,215 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# ======================================================================= #
|
||||
# Copyright (C) 2020 - 2023 Dominik Willner <th33xitus@gmail.com> #
|
||||
# #
|
||||
# This file is part of KIAUH - Klipper Installation And Update Helper #
|
||||
# https://github.com/dw-0/kiauh #
|
||||
# #
|
||||
# This file may be distributed under the terms of the GNU GPLv3 license #
|
||||
# ======================================================================= #
|
||||
|
||||
import os
|
||||
import re
|
||||
import grp
|
||||
import shutil
|
||||
import subprocess
|
||||
import textwrap
|
||||
|
||||
from typing import List, Union
|
||||
|
||||
from kiauh.core.config_manager.config_manager import ConfigManager
|
||||
from kiauh.core.instance_manager.instance_manager import InstanceManager
|
||||
from kiauh.modules.klipper import MODULE_PATH
|
||||
from kiauh.modules.klipper.klipper import Klipper
|
||||
from kiauh.modules.klipper.klipper_dialogs import (
|
||||
print_missing_usergroup_dialog,
|
||||
print_select_custom_name_dialog,
|
||||
)
|
||||
from kiauh.utils.constants import CURRENT_USER
|
||||
from kiauh.utils.input_utils import get_confirm, get_string_input
|
||||
from kiauh.utils.logger import Logger
|
||||
from kiauh.utils.system_utils import mask_system_service
|
||||
|
||||
|
||||
def assign_custom_names(
|
||||
instance_count: int, install_count: int, instance_list: List[Klipper] = None
|
||||
) -> List[str]:
|
||||
instance_names = []
|
||||
exclude = Klipper.blacklist()
|
||||
|
||||
# if an instance_list is provided, exclude all existing instance suffixes
|
||||
if instance_list is not None:
|
||||
for instance in instance_list:
|
||||
exclude.append(instance.suffix)
|
||||
|
||||
for i in range(instance_count + install_count):
|
||||
question = f"Enter name for instance {i + 1}"
|
||||
name = get_string_input(question, exclude=exclude)
|
||||
instance_names.append(name)
|
||||
exclude.append(name)
|
||||
|
||||
return instance_names
|
||||
|
||||
|
||||
def handle_convert_single_to_multi_instance_names(
|
||||
install_count: int,
|
||||
) -> Union[List[str], None]:
|
||||
print_select_custom_name_dialog()
|
||||
choice = get_confirm("Assign custom names?", False, allow_go_back=True)
|
||||
if choice is True:
|
||||
# instance_count = 0 and install_count + 1 as we want to assign a new name to the existing single install
|
||||
return assign_custom_names(0, install_count + 1)
|
||||
elif choice is False:
|
||||
# "install_count + 2" as we need to account for the existing single install
|
||||
_range = range(1, install_count + 2)
|
||||
return [str(i) for i in _range]
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def handle_new_multi_instance_names(
|
||||
instance_count: int, install_count: int
|
||||
) -> Union[List[str], None]:
|
||||
print_select_custom_name_dialog()
|
||||
choice = get_confirm("Assign custom names?", False, allow_go_back=True)
|
||||
if choice is True:
|
||||
return assign_custom_names(instance_count, install_count)
|
||||
elif choice is False:
|
||||
_range = range(1, install_count + 1)
|
||||
return [str(i) for i in _range]
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def handle_existing_multi_instance_names(
|
||||
instance_count: int, install_count: int, instance_list: List[Klipper]
|
||||
) -> List[str]:
|
||||
if has_custom_names(instance_list):
|
||||
return assign_custom_names(instance_count, install_count, instance_list)
|
||||
else:
|
||||
start = get_highest_index(instance_list) + 1
|
||||
_range = range(start, start + install_count)
|
||||
return [str(i) for i in _range]
|
||||
|
||||
|
||||
def handle_single_to_multi_conversion(
|
||||
instance_manager: InstanceManager, name: str
|
||||
) -> Klipper:
|
||||
instance_list = instance_manager.instances
|
||||
instance_manager.current_instance = instance_list[0]
|
||||
old_data_dir_name = instance_manager.instances[0].data_dir
|
||||
instance_manager.stop_instance()
|
||||
instance_manager.disable_instance()
|
||||
instance_manager.delete_instance(del_remnants=False)
|
||||
instance_manager.current_instance = Klipper(suffix=name)
|
||||
new_data_dir_name = instance_manager.current_instance.data_dir
|
||||
try:
|
||||
os.rename(old_data_dir_name, new_data_dir_name)
|
||||
return instance_manager.current_instance
|
||||
except OSError as e:
|
||||
log = f"Cannot rename {old_data_dir_name} to {new_data_dir_name}:\n{e}"
|
||||
Logger.print_error(log)
|
||||
|
||||
|
||||
def check_user_groups():
|
||||
current_groups = [grp.getgrgid(gid).gr_name for gid in os.getgroups()]
|
||||
|
||||
missing_groups = []
|
||||
if "tty" not in current_groups:
|
||||
missing_groups.append("tty")
|
||||
if "dialout" not in current_groups:
|
||||
missing_groups.append("dialout")
|
||||
|
||||
if not missing_groups:
|
||||
return
|
||||
|
||||
print_missing_usergroup_dialog(missing_groups)
|
||||
if not get_confirm(f"Add user '{CURRENT_USER}' to group(s) now?"):
|
||||
log = "Skipped adding user to required groups. You might encounter issues."
|
||||
Logger.warn(log)
|
||||
return
|
||||
|
||||
try:
|
||||
for group in missing_groups:
|
||||
Logger.print_status(f"Adding user '{CURRENT_USER}' to group {group} ...")
|
||||
command = ["sudo", "usermod", "-a", "-G", group, CURRENT_USER]
|
||||
subprocess.run(command, check=True)
|
||||
Logger.print_ok(f"Group {group} assigned to user '{CURRENT_USER}'.")
|
||||
except subprocess.CalledProcessError as e:
|
||||
Logger.print_error(f"Unable to add user to usergroups: {e}")
|
||||
raise
|
||||
|
||||
log = "Remember to relog/restart this machine for the group(s) to be applied!"
|
||||
Logger.print_warn(log)
|
||||
|
||||
|
||||
def handle_disruptive_system_packages() -> None:
|
||||
services = []
|
||||
|
||||
command = ["systemctl", "is-enabled", "brltty"]
|
||||
brltty_status = subprocess.run(command, capture_output=True, text=True)
|
||||
|
||||
command = ["systemctl", "is-enabled", "brltty-udev"]
|
||||
brltty_udev_status = subprocess.run(command, capture_output=True, text=True)
|
||||
|
||||
command = ["systemctl", "is-enabled", "ModemManager"]
|
||||
modem_manager_status = subprocess.run(command, capture_output=True, text=True)
|
||||
|
||||
if "enabled" in brltty_status.stdout:
|
||||
services.append("brltty")
|
||||
if "enabled" in brltty_udev_status.stdout:
|
||||
services.append("brltty-udev")
|
||||
if "enabled" in modem_manager_status.stdout:
|
||||
services.append("ModemManager")
|
||||
|
||||
for service in services if services else []:
|
||||
try:
|
||||
log = f"{service} service detected! Masking {service} service ..."
|
||||
Logger.print_status(log)
|
||||
mask_system_service(service)
|
||||
Logger.print_ok(f"{service} service masked!")
|
||||
except subprocess.CalledProcessError:
|
||||
warn_msg = textwrap.dedent(
|
||||
f"""
|
||||
KIAUH was unable to mask the {service} system service.
|
||||
Please fix the problem manually. Otherwise, this may have
|
||||
undesirable effects on the operation of Klipper.
|
||||
"""
|
||||
)[1:]
|
||||
Logger.print_warn(warn_msg)
|
||||
|
||||
|
||||
def has_custom_names(instance_list: List[Klipper]) -> bool:
|
||||
pattern = re.compile("^\d+$")
|
||||
for instance in instance_list:
|
||||
if not pattern.match(instance.suffix):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def get_highest_index(instance_list: List[Klipper]) -> int:
|
||||
indices = [int(instance.suffix.split("-")[-1]) for instance in instance_list]
|
||||
return max(indices)
|
||||
|
||||
|
||||
def create_example_printer_cfg(instance: Klipper) -> None:
|
||||
Logger.print_status(f"Creating example printer.cfg in '{instance.cfg_dir}'")
|
||||
if instance.cfg_file is not None:
|
||||
Logger.print_info(f"printer.cfg in '{instance.cfg_dir}' already exists.")
|
||||
return
|
||||
|
||||
source = os.path.join(MODULE_PATH, "res", "printer.cfg")
|
||||
target = os.path.join(instance.cfg_dir, "printer.cfg")
|
||||
try:
|
||||
shutil.copy(source, target)
|
||||
except OSError as e:
|
||||
Logger.print_error(f"Unable to create example printer.cfg:\n{e}")
|
||||
return
|
||||
|
||||
cm = ConfigManager(target)
|
||||
cm.read_config()
|
||||
cm.set_value("virtual_sdcard", "path", instance.gcodes_dir)
|
||||
cm.write_config()
|
||||
Logger.print_ok(f"Example printer.cfg created in '{instance.cfg_dir}'")
|
||||
1
kiauh/modules/klipper/res/klipper.env
Normal file
1
kiauh/modules/klipper/res/klipper.env
Normal file
@@ -0,0 +1 @@
|
||||
KLIPPER_ARGS="%KLIPPER_DIR%/klippy/klippy.py %CFG% -I %SERIAL% -l %LOG% -a %UDS%"
|
||||
18
kiauh/modules/klipper/res/klipper.service
Normal file
18
kiauh/modules/klipper/res/klipper.service
Normal file
@@ -0,0 +1,18 @@
|
||||
[Unit]
|
||||
Description=Klipper 3D Printer Firmware SV1
|
||||
Documentation=https://www.klipper3d.org/
|
||||
After=network-online.target
|
||||
Wants=udev.target
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=%USER%
|
||||
RemainAfterExit=yes
|
||||
WorkingDirectory=%KLIPPER_DIR%
|
||||
EnvironmentFile=%ENV_FILE%
|
||||
ExecStart=%ENV%/bin/python $KLIPPER_ARGS
|
||||
Restart=always
|
||||
RestartSec=10
|
||||
@@ -1,12 +1,9 @@
|
||||
[mcu]
|
||||
serial: /dev/serial/by-id/<your-mcu-id>
|
||||
|
||||
[pause_resume]
|
||||
|
||||
[display_status]
|
||||
|
||||
[virtual_sdcard]
|
||||
path: ~/gcode_files
|
||||
path: %GCODES_DIR%
|
||||
on_error_gcode: CANCEL_PRINT
|
||||
|
||||
[printer]
|
||||
kinematics: none
|
||||
26
kiauh/modules/mainsail/__init__.py
Normal file
26
kiauh/modules/mainsail/__init__.py
Normal file
@@ -0,0 +1,26 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# ======================================================================= #
|
||||
# Copyright (C) 2020 - 2023 Dominik Willner <th33xitus@gmail.com> #
|
||||
# #
|
||||
# This file is part of KIAUH - Klipper Installation And Update Helper #
|
||||
# https://github.com/dw-0/kiauh #
|
||||
# #
|
||||
# This file may be distributed under the terms of the GNU GPLv3 license #
|
||||
# ======================================================================= #
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import os
|
||||
|
||||
MODULE_PATH = os.path.dirname(os.path.abspath(__file__))
|
||||
MAINSAIL_DIR = os.path.join(Path.home(), "mainsail")
|
||||
MAINSAIL_CONFIG_DIR = os.path.join(Path.home(), "mainsail-config")
|
||||
MAINSAIL_CONFIG_JSON = os.path.join(MAINSAIL_DIR, "config.json")
|
||||
MAINSAIL_URL = (
|
||||
"https://github.com/mainsail-crew/mainsail/releases/latest/download/mainsail.zip"
|
||||
)
|
||||
MAINSAIL_UNSTABLE_URL = (
|
||||
"https://github.com/mainsail-crew/mainsail/releases/download/%TAG%/mainsail.zip"
|
||||
)
|
||||
MAINSAIL_CONFIG_REPO_URL = "https://github.com/mainsail-crew/mainsail-config.git"
|
||||
95
kiauh/modules/mainsail/mainsail_dialogs.py
Normal file
95
kiauh/modules/mainsail/mainsail_dialogs.py
Normal file
@@ -0,0 +1,95 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# ======================================================================= #
|
||||
# Copyright (C) 2020 - 2023 Dominik Willner <th33xitus@gmail.com> #
|
||||
# #
|
||||
# This file is part of KIAUH - Klipper Installation And Update Helper #
|
||||
# https://github.com/dw-0/kiauh #
|
||||
# #
|
||||
# This file may be distributed under the terms of the GNU GPLv3 license #
|
||||
# ======================================================================= #
|
||||
|
||||
import textwrap
|
||||
|
||||
from kiauh.core.menus.base_menu import print_back_footer
|
||||
from kiauh.utils.constants import RESET_FORMAT, COLOR_YELLOW, COLOR_CYAN
|
||||
|
||||
|
||||
def print_moonraker_not_found_dialog():
|
||||
line1 = f"{COLOR_YELLOW}WARNING:{RESET_FORMAT}"
|
||||
line2 = f"{COLOR_YELLOW}No local Moonraker installation was found!{RESET_FORMAT}"
|
||||
dialog = textwrap.dedent(
|
||||
f"""
|
||||
/=======================================================\\
|
||||
| {line1:<63}|
|
||||
| {line2:<63}|
|
||||
|-------------------------------------------------------|
|
||||
| It is possible to install Mainsail without a local |
|
||||
| Moonraker installation. If you continue, you need to |
|
||||
| make sure, that Moonraker is installed on another |
|
||||
| machine in your network. Otherwise Mainsail will NOT |
|
||||
| work correctly. |
|
||||
"""
|
||||
)[1:]
|
||||
|
||||
print(dialog, end="")
|
||||
print_back_footer()
|
||||
|
||||
|
||||
def print_mainsail_already_installed_dialog():
|
||||
line1 = f"{COLOR_YELLOW}WARNING:{RESET_FORMAT}"
|
||||
line2 = f"{COLOR_YELLOW}Mainsail seems to be already installed!{RESET_FORMAT}"
|
||||
dialog = textwrap.dedent(
|
||||
f"""
|
||||
/=======================================================\\
|
||||
| {line1:<63}|
|
||||
| {line2:<63}|
|
||||
|-------------------------------------------------------|
|
||||
| If you continue, your current Mainsail installation |
|
||||
| will be overwritten. You will not loose any printer |
|
||||
| configurations and the Moonraker database will remain |
|
||||
| untouched. |
|
||||
"""
|
||||
)[1:]
|
||||
|
||||
print(dialog, end="")
|
||||
print_back_footer()
|
||||
|
||||
|
||||
def print_install_mainsail_config_dialog():
|
||||
dialog = textwrap.dedent(
|
||||
f"""
|
||||
/=======================================================\\
|
||||
| It is recommended to use special macros in order to |
|
||||
| have Mainsail fully functional and working. |
|
||||
| |
|
||||
| The recommended macros for Mainsail can be seen here: |
|
||||
| https://github.com/mainsail-crew/mainsail-config |
|
||||
| |
|
||||
| If you already use these macros skip this step. |
|
||||
| Otherwise you should consider to answer with 'Y' to |
|
||||
| download the recommended macros. |
|
||||
\\=======================================================/
|
||||
"""
|
||||
)[1:]
|
||||
|
||||
print(dialog, end="")
|
||||
|
||||
|
||||
def print_mainsail_port_select_dialog(port: str):
|
||||
port = f"{COLOR_CYAN}{port}{RESET_FORMAT}"
|
||||
dialog = textwrap.dedent(
|
||||
f"""
|
||||
/=======================================================\\
|
||||
| Please select the port, Mainsail should be served on. |
|
||||
| If you are unsure what to select, hit Enter to apply |
|
||||
| the suggested value of: {port:38} |
|
||||
| |
|
||||
| In case you need Mainsail to be served on a specific |
|
||||
| port, you can set it now. Make sure the port is not |
|
||||
| used by any other application on your system! |
|
||||
\\=======================================================/
|
||||
"""
|
||||
)[1:]
|
||||
|
||||
print(dialog, end="")
|
||||
162
kiauh/modules/mainsail/mainsail_remove.py
Normal file
162
kiauh/modules/mainsail/mainsail_remove.py
Normal file
@@ -0,0 +1,162 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# ======================================================================= #
|
||||
# Copyright (C) 2020 - 2023 Dominik Willner <th33xitus@gmail.com> #
|
||||
# #
|
||||
# This file is part of KIAUH - Klipper Installation And Update Helper #
|
||||
# https://github.com/dw-0/kiauh #
|
||||
# #
|
||||
# This file may be distributed under the terms of the GNU GPLv3 license #
|
||||
# ======================================================================= #
|
||||
|
||||
|
||||
import shutil
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
from kiauh.core.config_manager.config_manager import ConfigManager
|
||||
from kiauh.core.instance_manager.instance_manager import InstanceManager
|
||||
from kiauh.modules.klipper.klipper import Klipper
|
||||
from kiauh.modules.mainsail import MAINSAIL_DIR, MAINSAIL_CONFIG_DIR
|
||||
from kiauh.modules.mainsail.mainsail_utils import backup_config_json
|
||||
from kiauh.modules.moonraker.moonraker import Moonraker
|
||||
from kiauh.utils import NGINX_SITES_AVAILABLE, NGINX_SITES_ENABLED
|
||||
from kiauh.utils.filesystem_utils import remove_file
|
||||
from kiauh.utils.logger import Logger
|
||||
|
||||
|
||||
def run_mainsail_removal(
|
||||
remove_mainsail: bool,
|
||||
remove_ms_config: bool,
|
||||
backup_ms_config_json: bool,
|
||||
remove_mr_updater_section: bool,
|
||||
remove_msc_printer_cfg_include: bool,
|
||||
) -> None:
|
||||
if backup_ms_config_json:
|
||||
backup_config_json()
|
||||
if remove_mainsail:
|
||||
remove_mainsail_dir()
|
||||
remove_nginx_config()
|
||||
remove_nginx_logs()
|
||||
if remove_mr_updater_section:
|
||||
remove_updater_section("update_manager mainsail")
|
||||
if remove_ms_config:
|
||||
remove_mainsail_cfg_dir()
|
||||
remove_mainsail_cfg_symlink()
|
||||
if remove_mr_updater_section:
|
||||
remove_updater_section("update_manager mainsail-config")
|
||||
if remove_msc_printer_cfg_include:
|
||||
remove_printer_cfg_include()
|
||||
|
||||
|
||||
def remove_mainsail_dir() -> None:
|
||||
Logger.print_status("Removing Mainsail ...")
|
||||
if not Path(MAINSAIL_DIR).exists():
|
||||
Logger.print_info(f"'{MAINSAIL_DIR}' does not exist. Skipping ...")
|
||||
return
|
||||
|
||||
try:
|
||||
shutil.rmtree(MAINSAIL_DIR)
|
||||
except OSError as e:
|
||||
Logger.print_error(f"Unable to delete '{MAINSAIL_DIR}':\n{e}")
|
||||
|
||||
|
||||
def remove_nginx_config() -> None:
|
||||
Logger.print_status("Removing Mainsails NGINX config ...")
|
||||
try:
|
||||
remove_file(Path(NGINX_SITES_AVAILABLE, "mainsail"), True)
|
||||
remove_file(Path(NGINX_SITES_ENABLED, "mainsail"), True)
|
||||
|
||||
except subprocess.CalledProcessError as e:
|
||||
log = f"Unable to remove Mainsail NGINX config:\n{e.stderr.decode()}"
|
||||
Logger.print_error(log)
|
||||
|
||||
|
||||
def remove_nginx_logs() -> None:
|
||||
Logger.print_status("Removing Mainsails NGINX logs ...")
|
||||
try:
|
||||
remove_file(Path("/var/log/nginx/mainsail-access.log"), True)
|
||||
remove_file(Path("/var/log/nginx/mainsail-error.log"), True)
|
||||
|
||||
im = InstanceManager(Klipper)
|
||||
if not im.instances:
|
||||
return
|
||||
|
||||
for instance in im.instances:
|
||||
remove_file(Path(instance.log_dir, "mainsail-access.log"))
|
||||
remove_file(Path(instance.log_dir, "mainsail-error.log"))
|
||||
|
||||
except (OSError, subprocess.CalledProcessError) as e:
|
||||
Logger.print_error(f"Unable to NGINX logs:\n{e}")
|
||||
|
||||
|
||||
def remove_updater_section(name: str) -> None:
|
||||
Logger.print_status("Remove updater section from moonraker.conf ...")
|
||||
im = InstanceManager(Moonraker)
|
||||
if not im.instances:
|
||||
Logger.print_info("Moonraker not installed. Skipping ...")
|
||||
return
|
||||
|
||||
for instance in im.instances:
|
||||
log = f"Remove section '{name}' in '{instance.cfg_file}' ..."
|
||||
Logger.print_status(log)
|
||||
|
||||
if not Path(instance.cfg_file).exists():
|
||||
Logger.print_info("Section not present. Skipping ...")
|
||||
continue
|
||||
|
||||
cm = ConfigManager(instance.cfg_file)
|
||||
cm.read_config()
|
||||
if not cm.config.has_section(name):
|
||||
Logger.print_info("Section not present. Skipped ...")
|
||||
continue
|
||||
|
||||
cm.config.remove_section(name)
|
||||
cm.write_config()
|
||||
|
||||
|
||||
def remove_mainsail_cfg_dir() -> None:
|
||||
Logger.print_status("Removing mainsail-config ...")
|
||||
if not Path(MAINSAIL_CONFIG_DIR).exists():
|
||||
Logger.print_info(f"'{MAINSAIL_CONFIG_DIR}' does not exist. Skipping ...")
|
||||
return
|
||||
|
||||
try:
|
||||
shutil.rmtree(MAINSAIL_CONFIG_DIR)
|
||||
except OSError as e:
|
||||
Logger.print_error(f"Unable to delete '{MAINSAIL_CONFIG_DIR}':\n{e}")
|
||||
|
||||
|
||||
def remove_mainsail_cfg_symlink() -> None:
|
||||
Logger.print_status("Removing mainsail.cfg symlinks ...")
|
||||
im = InstanceManager(Moonraker)
|
||||
for instance in im.instances:
|
||||
Logger.print_status(f"Removing symlink from '{instance.cfg_dir}' ...")
|
||||
try:
|
||||
remove_file(Path(instance.cfg_dir, "mainsail.cfg"))
|
||||
except subprocess.CalledProcessError:
|
||||
Logger.print_error("Failed to remove symlink!")
|
||||
|
||||
|
||||
def remove_printer_cfg_include() -> None:
|
||||
Logger.print_status("Remove mainsail-config include from printer.cfg ...")
|
||||
im = InstanceManager(Klipper)
|
||||
if not im.instances:
|
||||
Logger.print_info("Klipper not installed. Skipping ...")
|
||||
return
|
||||
|
||||
for instance in im.instances:
|
||||
log = f"Removing include from '{instance.cfg_file}' ..."
|
||||
Logger.print_status(log)
|
||||
|
||||
if not Path(instance.cfg_file).exists():
|
||||
continue
|
||||
|
||||
cm = ConfigManager(instance.cfg_file)
|
||||
cm.read_config()
|
||||
if not cm.config.has_section("include mainsail.cfg"):
|
||||
Logger.print_info("Section not present. Skipped ...")
|
||||
continue
|
||||
|
||||
cm.config.remove_section("include mainsail.cfg")
|
||||
cm.write_config()
|
||||
247
kiauh/modules/mainsail/mainsail_setup.py
Normal file
247
kiauh/modules/mainsail/mainsail_setup.py
Normal file
@@ -0,0 +1,247 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# ======================================================================= #
|
||||
# Copyright (C) 2020 - 2023 Dominik Willner <th33xitus@gmail.com> #
|
||||
# #
|
||||
# This file is part of KIAUH - Klipper Installation And Update Helper #
|
||||
# https://github.com/dw-0/kiauh #
|
||||
# #
|
||||
# This file may be distributed under the terms of the GNU GPLv3 license #
|
||||
# ======================================================================= #
|
||||
|
||||
import os.path
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
from typing import List
|
||||
|
||||
from kiauh import KIAUH_CFG
|
||||
from kiauh.core.config_manager.config_manager import ConfigManager
|
||||
from kiauh.core.instance_manager.instance_manager import InstanceManager
|
||||
from kiauh.core.repo_manager.repo_manager import RepoManager
|
||||
from kiauh.modules.klipper.klipper import Klipper
|
||||
from kiauh.modules.mainsail import (
|
||||
MAINSAIL_URL,
|
||||
MAINSAIL_DIR,
|
||||
MAINSAIL_CONFIG_DIR,
|
||||
MAINSAIL_CONFIG_REPO_URL,
|
||||
MODULE_PATH,
|
||||
)
|
||||
from kiauh.modules.mainsail.mainsail_dialogs import (
|
||||
print_moonraker_not_found_dialog,
|
||||
print_mainsail_already_installed_dialog,
|
||||
print_install_mainsail_config_dialog,
|
||||
print_mainsail_port_select_dialog,
|
||||
)
|
||||
from kiauh.modules.mainsail.mainsail_utils import (
|
||||
restore_config_json,
|
||||
enable_mainsail_remotemode,
|
||||
backup_config_json,
|
||||
symlink_webui_nginx_log,
|
||||
)
|
||||
from kiauh.modules.moonraker.moonraker import Moonraker
|
||||
from kiauh.utils import NGINX_SITES_AVAILABLE, NGINX_SITES_ENABLED
|
||||
from kiauh.utils.common import check_install_dependencies
|
||||
from kiauh.utils.filesystem_utils import (
|
||||
unzip,
|
||||
copy_upstream_nginx_cfg,
|
||||
copy_common_vars_nginx_cfg,
|
||||
create_nginx_cfg,
|
||||
create_symlink,
|
||||
remove_file,
|
||||
)
|
||||
from kiauh.utils.input_utils import get_confirm, get_number_input
|
||||
from kiauh.utils.logger import Logger
|
||||
from kiauh.utils.system_utils import (
|
||||
download_file,
|
||||
set_nginx_permissions,
|
||||
get_ipv4_addr,
|
||||
control_systemd_service,
|
||||
)
|
||||
|
||||
|
||||
def run_mainsail_installation() -> None:
|
||||
im_mr = InstanceManager(Moonraker)
|
||||
is_moonraker_installed = len(im_mr.instances) > 0
|
||||
|
||||
enable_remotemode = False
|
||||
if not is_moonraker_installed:
|
||||
print_moonraker_not_found_dialog()
|
||||
do_continue = get_confirm("Continue Mainsail installation?", allow_go_back=True)
|
||||
if do_continue:
|
||||
enable_remotemode = True
|
||||
else:
|
||||
return
|
||||
|
||||
is_mainsail_installed = Path(f"{Path.home()}/mainsail").exists()
|
||||
do_reinstall = False
|
||||
if is_mainsail_installed:
|
||||
print_mainsail_already_installed_dialog()
|
||||
do_reinstall = get_confirm("Re-install Mainsail?", allow_go_back=True)
|
||||
if do_reinstall:
|
||||
backup_config_json()
|
||||
else:
|
||||
return
|
||||
|
||||
im_kl = InstanceManager(Klipper)
|
||||
is_klipper_installed = len(im_kl.instances) > 0
|
||||
install_ms_config = False
|
||||
if is_klipper_installed:
|
||||
print_install_mainsail_config_dialog()
|
||||
question = "Download the recommended macros?"
|
||||
install_ms_config = get_confirm(question, allow_go_back=False)
|
||||
|
||||
cm = ConfigManager(cfg_file=KIAUH_CFG)
|
||||
cm.read_config()
|
||||
default_port = cm.get_value("mainsail", "default_port")
|
||||
mainsail_port = default_port if default_port else 80
|
||||
if not default_port:
|
||||
print_mainsail_port_select_dialog(f"{mainsail_port}")
|
||||
mainsail_port = get_number_input(
|
||||
"Configure Mainsail for port",
|
||||
min_count=mainsail_port,
|
||||
default=mainsail_port,
|
||||
)
|
||||
|
||||
check_install_dependencies(["nginx"])
|
||||
|
||||
try:
|
||||
download_mainsail()
|
||||
if do_reinstall:
|
||||
restore_config_json()
|
||||
if enable_remotemode:
|
||||
enable_mainsail_remotemode()
|
||||
if is_moonraker_installed:
|
||||
patch_moonraker_conf(
|
||||
im_mr.instances,
|
||||
"Mainsail",
|
||||
"update_manager mainsail",
|
||||
"mainsail-updater.conf",
|
||||
)
|
||||
im_mr.restart_all_instance()
|
||||
if install_ms_config and is_klipper_installed:
|
||||
download_mainsail_cfg()
|
||||
create_mainsail_cfg_symlink(im_kl.instances)
|
||||
patch_moonraker_conf(
|
||||
im_mr.instances,
|
||||
"mainsail-config",
|
||||
"update_manager mainsail-config",
|
||||
"mainsail-config-updater.conf",
|
||||
)
|
||||
patch_printer_config(im_kl.instances)
|
||||
im_kl.restart_all_instance()
|
||||
|
||||
copy_upstream_nginx_cfg()
|
||||
copy_common_vars_nginx_cfg()
|
||||
create_mainsail_nginx_cfg(mainsail_port)
|
||||
if is_klipper_installed:
|
||||
symlink_webui_nginx_log(im_kl.instances)
|
||||
control_systemd_service("nginx", "restart")
|
||||
|
||||
except Exception as e:
|
||||
Logger.print_error(f"Mainsail installation failed!\n{e}")
|
||||
return
|
||||
|
||||
log = f"Open Mainsail now on: http://{get_ipv4_addr()}:{mainsail_port}"
|
||||
Logger.print_ok("Mainsail installation complete!", start="\n")
|
||||
Logger.print_ok(log, prefix=False, end="\n\n")
|
||||
|
||||
|
||||
def download_mainsail() -> None:
|
||||
try:
|
||||
Logger.print_status("Downloading Mainsail ...")
|
||||
download_file(MAINSAIL_URL, f"{Path.home()}", "mainsail.zip")
|
||||
Logger.print_ok("Download complete!")
|
||||
|
||||
Logger.print_status("Extracting mainsail.zip ...")
|
||||
unzip(f"{Path.home()}/mainsail.zip", MAINSAIL_DIR)
|
||||
Logger.print_ok("OK!")
|
||||
|
||||
except Exception:
|
||||
Logger.print_error("Downloading Mainsail failed!")
|
||||
raise
|
||||
|
||||
|
||||
def download_mainsail_cfg() -> None:
|
||||
try:
|
||||
Logger.print_status("Downloading mainsail-config ...")
|
||||
rm = RepoManager(MAINSAIL_CONFIG_REPO_URL, target_dir=MAINSAIL_CONFIG_DIR)
|
||||
rm.clone_repo()
|
||||
except Exception:
|
||||
Logger.print_error("Downloading mainsail-config failed!")
|
||||
raise
|
||||
|
||||
|
||||
def create_mainsail_cfg_symlink(klipper_instances: List[Klipper]) -> None:
|
||||
Logger.print_status("Create symlink of mainsail.cfg ...")
|
||||
source = Path(MAINSAIL_CONFIG_DIR, "mainsail.cfg")
|
||||
for instance in klipper_instances:
|
||||
target = instance.cfg_dir
|
||||
Logger.print_status(f"Linking {source} to {target}")
|
||||
try:
|
||||
create_symlink(source, target)
|
||||
except subprocess.CalledProcessError:
|
||||
Logger.print_error("Creating symlink failed!")
|
||||
|
||||
|
||||
def create_mainsail_nginx_cfg(port: int) -> None:
|
||||
root_dir = MAINSAIL_DIR
|
||||
source = Path(NGINX_SITES_AVAILABLE, "mainsail")
|
||||
target = Path(NGINX_SITES_ENABLED, "mainsail")
|
||||
try:
|
||||
Logger.print_status("Creating NGINX config for Mainsail ...")
|
||||
remove_file(Path("/etc/nginx/sites-enabled/default"), True)
|
||||
create_nginx_cfg("mainsail", port, root_dir)
|
||||
create_symlink(source, target, True)
|
||||
set_nginx_permissions()
|
||||
Logger.print_ok("NGINX config for Mainsail successfully created.")
|
||||
except Exception:
|
||||
Logger.print_error("Creating NGINX config for Mainsail failed!")
|
||||
raise
|
||||
|
||||
|
||||
def patch_moonraker_conf(
|
||||
moonraker_instances: List[Moonraker],
|
||||
name: str,
|
||||
section_name: str,
|
||||
template_file: str,
|
||||
) -> None:
|
||||
for instance in moonraker_instances:
|
||||
cfg_file = instance.cfg_file
|
||||
Logger.print_status(f"Add {name} update section to '{cfg_file}' ...")
|
||||
|
||||
if not Path(cfg_file).exists():
|
||||
Logger.print_warn(f"'{cfg_file}' not found!")
|
||||
return
|
||||
|
||||
cm = ConfigManager(cfg_file)
|
||||
cm.read_config()
|
||||
if cm.config.has_section(section_name):
|
||||
Logger.print_info("Section already exist. Skipped ...")
|
||||
return
|
||||
|
||||
template = os.path.join(MODULE_PATH, "res", template_file)
|
||||
with open(template, "r") as t:
|
||||
template_content = "\n"
|
||||
template_content += t.read()
|
||||
|
||||
with open(cfg_file, "a") as f:
|
||||
f.write(template_content)
|
||||
|
||||
|
||||
def patch_printer_config(klipper_instances: List[Klipper]) -> None:
|
||||
for instance in klipper_instances:
|
||||
cfg_file = instance.cfg_file
|
||||
Logger.print_status(f"Including mainsail-config in '{cfg_file}' ...")
|
||||
|
||||
if not Path(cfg_file).exists():
|
||||
Logger.print_warn(f"'{cfg_file}' not found!")
|
||||
return
|
||||
|
||||
cm = ConfigManager(cfg_file)
|
||||
cm.read_config()
|
||||
if cm.config.has_section("include mainsail.cfg"):
|
||||
Logger.print_info("Section already exist. Skipped ...")
|
||||
return
|
||||
|
||||
with open(cfg_file, "a") as f:
|
||||
f.write("\n[include mainsail.cfg]")
|
||||
69
kiauh/modules/mainsail/mainsail_utils.py
Normal file
69
kiauh/modules/mainsail/mainsail_utils.py
Normal file
@@ -0,0 +1,69 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# ======================================================================= #
|
||||
# Copyright (C) 2020 - 2023 Dominik Willner <th33xitus@gmail.com> #
|
||||
# #
|
||||
# This file is part of KIAUH - Klipper Installation And Update Helper #
|
||||
# https://github.com/dw-0/kiauh #
|
||||
# #
|
||||
# This file may be distributed under the terms of the GNU GPLv3 license #
|
||||
# ======================================================================= #
|
||||
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
from typing import List
|
||||
|
||||
from kiauh.modules.klipper.klipper import Klipper
|
||||
from kiauh.modules.mainsail import MAINSAIL_CONFIG_JSON
|
||||
from kiauh.utils.logger import Logger
|
||||
|
||||
|
||||
# TODO: give this method an optional target dir to backup to
|
||||
# alteratively use the BackupManager for handling this task (probably best)
|
||||
def backup_config_json() -> None:
|
||||
try:
|
||||
Logger.print_status(f"Backup '{MAINSAIL_CONFIG_JSON}' ...")
|
||||
target = os.path.join(Path.home(), "config.json.kiauh.bak")
|
||||
shutil.copy(MAINSAIL_CONFIG_JSON, target)
|
||||
except OSError:
|
||||
Logger.print_info(f"Unable to backup config.json. Skipped ...")
|
||||
|
||||
|
||||
def restore_config_json() -> None:
|
||||
try:
|
||||
Logger.print_status(f"Restore '{MAINSAIL_CONFIG_JSON}' ...")
|
||||
source = os.path.join(Path.home(), "config.json.kiauh.bak")
|
||||
shutil.copy(source, MAINSAIL_CONFIG_JSON)
|
||||
except OSError:
|
||||
Logger.print_info(f"Unable to restore config.json. Skipped ...")
|
||||
|
||||
|
||||
def enable_mainsail_remotemode() -> None:
|
||||
with open(MAINSAIL_CONFIG_JSON, "r") as f:
|
||||
config_data = json.load(f)
|
||||
|
||||
if config_data["instancesDB"] == "browser":
|
||||
return
|
||||
|
||||
Logger.print_status("Setting instance storage location to 'browser' ...")
|
||||
config_data["instancesDB"] = "browser"
|
||||
|
||||
with open(MAINSAIL_CONFIG_JSON, "w") as f:
|
||||
json.dump(config_data, f, indent=4)
|
||||
|
||||
|
||||
def symlink_webui_nginx_log(klipper_instances: List[Klipper]) -> None:
|
||||
Logger.print_status("Link NGINX logs into log directory ...")
|
||||
access_log = Path("/var/log/nginx/mainsail-access.log")
|
||||
error_log = Path("/var/log/nginx/mainsail-error.log")
|
||||
|
||||
for instance in klipper_instances:
|
||||
desti_access = Path(instance.log_dir).joinpath("mainsail-access.log")
|
||||
if not desti_access.exists():
|
||||
desti_access.symlink_to(access_log)
|
||||
|
||||
desti_error = Path(instance.log_dir).joinpath("mainsail-error.log")
|
||||
if not desti_error.exists():
|
||||
desti_error.symlink_to(error_log)
|
||||
0
kiauh/modules/mainsail/menus/__init__.py
Normal file
0
kiauh/modules/mainsail/menus/__init__.py
Normal file
121
kiauh/modules/mainsail/menus/mainsail_remove_menu.py
Normal file
121
kiauh/modules/mainsail/menus/mainsail_remove_menu.py
Normal file
@@ -0,0 +1,121 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# ======================================================================= #
|
||||
# Copyright (C) 2020 - 2023 Dominik Willner <th33xitus@gmail.com> #
|
||||
# #
|
||||
# This file is part of KIAUH - Klipper Installation And Update Helper #
|
||||
# https://github.com/dw-0/kiauh #
|
||||
# #
|
||||
# This file may be distributed under the terms of the GNU GPLv3 license #
|
||||
# ======================================================================= #
|
||||
|
||||
import textwrap
|
||||
|
||||
from kiauh.core.menus import BACK_HELP_FOOTER
|
||||
from kiauh.core.menus.base_menu import BaseMenu
|
||||
from kiauh.modules.mainsail import mainsail_remove
|
||||
from kiauh.utils.constants import RESET_FORMAT, COLOR_RED, COLOR_CYAN
|
||||
|
||||
|
||||
class MainsailRemoveMenu(BaseMenu):
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
header=False,
|
||||
options={
|
||||
0: self.toggle_all,
|
||||
1: self.toggle_remove_mainsail,
|
||||
2: self.toggle_remove_ms_config,
|
||||
3: self.toggle_backup_config_json,
|
||||
4: self.toggle_remove_updater_section,
|
||||
5: self.toggle_remove_printer_cfg_include,
|
||||
6: self.run_removal_process,
|
||||
},
|
||||
footer_type=BACK_HELP_FOOTER,
|
||||
)
|
||||
self.remove_mainsail = False
|
||||
self.remove_ms_config = False
|
||||
self.backup_config_json = False
|
||||
self.remove_updater_section = False
|
||||
self.remove_printer_cfg_include = False
|
||||
|
||||
def print_menu(self) -> None:
|
||||
header = " [ Remove Mainsail ] "
|
||||
color = COLOR_RED
|
||||
count = 62 - len(color) - len(RESET_FORMAT)
|
||||
checked = f"[{COLOR_CYAN}x{RESET_FORMAT}]"
|
||||
unchecked = "[ ]"
|
||||
o1 = checked if self.remove_mainsail else unchecked
|
||||
o2 = checked if self.remove_ms_config else unchecked
|
||||
o3 = checked if self.backup_config_json else unchecked
|
||||
o4 = checked if self.remove_updater_section else unchecked
|
||||
o5 = checked if self.remove_printer_cfg_include else unchecked
|
||||
menu = textwrap.dedent(
|
||||
f"""
|
||||
/=======================================================\\
|
||||
| {color}{header:~^{count}}{RESET_FORMAT} |
|
||||
|-------------------------------------------------------|
|
||||
| Enter a number and hit enter to select / deselect |
|
||||
| the specific option for removal. |
|
||||
|-------------------------------------------------------|
|
||||
| 0) Everything |
|
||||
|-------------------------------------------------------|
|
||||
| 1) {o1} Remove Mainsail Web UI |
|
||||
| 2) {o2} Remove mainsail-config |
|
||||
| 3) {o3} Backup Mainsail config.json |
|
||||
| |
|
||||
| printer.cfg & moonraker.conf |
|
||||
| 4) {o4} Remove Moonraker updater section |
|
||||
| 5) {o5} Remove printer.cfg include |
|
||||
|-------------------------------------------------------|
|
||||
| 6) Start removal |
|
||||
"""
|
||||
)[1:]
|
||||
print(menu, end="")
|
||||
|
||||
def toggle_all(self) -> None:
|
||||
self.remove_mainsail = True
|
||||
self.remove_ms_config = True
|
||||
self.backup_config_json = True
|
||||
self.remove_updater_section = True
|
||||
self.remove_printer_cfg_include = True
|
||||
|
||||
def toggle_remove_mainsail(self) -> None:
|
||||
self.remove_mainsail = not self.remove_mainsail
|
||||
|
||||
def toggle_remove_ms_config(self) -> None:
|
||||
self.remove_ms_config = not self.remove_ms_config
|
||||
|
||||
def toggle_backup_config_json(self) -> None:
|
||||
self.backup_config_json = not self.backup_config_json
|
||||
|
||||
def toggle_remove_updater_section(self) -> None:
|
||||
self.remove_updater_section = not self.remove_updater_section
|
||||
|
||||
def toggle_remove_printer_cfg_include(self) -> None:
|
||||
self.remove_printer_cfg_include = not self.remove_printer_cfg_include
|
||||
|
||||
def run_removal_process(self) -> None:
|
||||
if (
|
||||
not self.remove_mainsail
|
||||
and not self.remove_ms_config
|
||||
and not self.backup_config_json
|
||||
and not self.remove_updater_section
|
||||
and not self.remove_printer_cfg_include
|
||||
):
|
||||
error = f"{COLOR_RED}Nothing selected! Select options to remove first.{RESET_FORMAT}"
|
||||
print(error)
|
||||
return
|
||||
|
||||
mainsail_remove.run_mainsail_removal(
|
||||
remove_mainsail=self.remove_mainsail,
|
||||
remove_ms_config=self.remove_ms_config,
|
||||
backup_ms_config_json=self.backup_config_json,
|
||||
remove_mr_updater_section=self.remove_updater_section,
|
||||
remove_msc_printer_cfg_include=self.remove_printer_cfg_include,
|
||||
)
|
||||
|
||||
self.remove_mainsail = False
|
||||
self.remove_ms_config = False
|
||||
self.backup_config_json = False
|
||||
self.remove_updater_section = False
|
||||
self.remove_printer_cfg_include = False
|
||||
6
kiauh/modules/mainsail/res/mainsail-config-updater.conf
Normal file
6
kiauh/modules/mainsail/res/mainsail-config-updater.conf
Normal file
@@ -0,0 +1,6 @@
|
||||
[update_manager mainsail-config]
|
||||
type: git_repo
|
||||
primary_branch: master
|
||||
path: ~/mainsail-config
|
||||
origin: https://github.com/mainsail-crew/mainsail-config.git
|
||||
managed_services: klipper
|
||||
5
kiauh/modules/mainsail/res/mainsail-updater.conf
Normal file
5
kiauh/modules/mainsail/res/mainsail-updater.conf
Normal file
@@ -0,0 +1,5 @@
|
||||
[update_manager mainsail]
|
||||
type: web
|
||||
channel: stable
|
||||
repo: mainsail-crew/mainsail
|
||||
path: ~/mainsail
|
||||
31
kiauh/modules/moonraker/__init__.py
Normal file
31
kiauh/modules/moonraker/__init__.py
Normal file
@@ -0,0 +1,31 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# ======================================================================= #
|
||||
# Copyright (C) 2020 - 2023 Dominik Willner <th33xitus@gmail.com> #
|
||||
# #
|
||||
# This file is part of KIAUH - Klipper Installation And Update Helper #
|
||||
# https://github.com/dw-0/kiauh #
|
||||
# #
|
||||
# This file may be distributed under the terms of the GNU GPLv3 license #
|
||||
# ======================================================================= #
|
||||
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
MODULE_PATH = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
MOONRAKER_DIR = f"{Path.home()}/moonraker"
|
||||
MOONRAKER_ENV_DIR = f"{Path.home()}/moonraker-env"
|
||||
MOONRAKER_REQUIREMENTS_TXT = f"{MOONRAKER_DIR}/scripts/moonraker-requirements.txt"
|
||||
DEFAULT_MOONRAKER_REPO_URL = "https://github.com/Arksine/moonraker"
|
||||
DEFAULT_MOONRAKER_PORT = 7125
|
||||
|
||||
# introduced due to
|
||||
# https://github.com/Arksine/moonraker/issues/349
|
||||
# https://github.com/Arksine/moonraker/pull/346
|
||||
POLKIT_LEGACY_FILE = "/etc/polkit-1/localauthority/50-local.d/10-moonraker.pkla"
|
||||
POLKIT_FILE = "/etc/polkit-1/rules.d/moonraker.rules"
|
||||
POLKIT_USR_FILE = "/usr/share/polkit-1/rules.d/moonraker.rules"
|
||||
POLKIT_SCRIPT = f"{Path.home()}/moonraker/scripts/set-policykit-rules.sh"
|
||||
|
||||
EXIT_MOONRAKER_SETUP = "Exiting Moonraker setup ..."
|
||||
162
kiauh/modules/moonraker/moonraker.py
Normal file
162
kiauh/modules/moonraker/moonraker.py
Normal file
@@ -0,0 +1,162 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# ======================================================================= #
|
||||
# Copyright (C) 2020 - 2023 Dominik Willner <th33xitus@gmail.com> #
|
||||
# #
|
||||
# This file is part of KIAUH - Klipper Installation And Update Helper #
|
||||
# https://github.com/dw-0/kiauh #
|
||||
# #
|
||||
# This file may be distributed under the terms of the GNU GPLv3 license #
|
||||
# ======================================================================= #
|
||||
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
from typing import List, Union
|
||||
|
||||
from kiauh.core.config_manager.config_manager import ConfigManager
|
||||
from kiauh.core.instance_manager.base_instance import BaseInstance
|
||||
from kiauh.modules.moonraker import MOONRAKER_DIR, MOONRAKER_ENV_DIR, MODULE_PATH
|
||||
from kiauh.utils.constants import SYSTEMD
|
||||
from kiauh.utils.logger import Logger
|
||||
|
||||
|
||||
# noinspection PyMethodMayBeStatic
|
||||
class Moonraker(BaseInstance):
|
||||
@classmethod
|
||||
def blacklist(cls) -> List[str]:
|
||||
return ["None", "mcu"]
|
||||
|
||||
def __init__(self, suffix: str = None):
|
||||
super().__init__(instance_type=self, suffix=suffix)
|
||||
self.moonraker_dir = MOONRAKER_DIR
|
||||
self.env_dir = MOONRAKER_ENV_DIR
|
||||
self.cfg_file = self._get_cfg()
|
||||
self.port = self._get_port()
|
||||
self.backup_dir = f"{self.data_dir}/backup"
|
||||
self.certs_dir = f"{self.data_dir}/certs"
|
||||
self.db_dir = f"{self.data_dir}/database"
|
||||
self.log = f"{self.log_dir}/moonraker.log"
|
||||
|
||||
def create(self, create_example_cfg: bool = False) -> None:
|
||||
Logger.print_status("Creating new Moonraker Instance ...")
|
||||
service_template_path = os.path.join(MODULE_PATH, "res", "moonraker.service")
|
||||
env_template_file_path = os.path.join(MODULE_PATH, "res", "moonraker.env")
|
||||
service_file_name = self.get_service_file_name(extension=True)
|
||||
service_file_target = f"{SYSTEMD}/{service_file_name}"
|
||||
env_file_target = os.path.abspath(f"{self.sysd_dir}/moonraker.env")
|
||||
|
||||
try:
|
||||
self.create_folders([self.backup_dir, self.certs_dir, self.db_dir])
|
||||
self.write_service_file(
|
||||
service_template_path, service_file_target, env_file_target
|
||||
)
|
||||
self.write_env_file(env_template_file_path, env_file_target)
|
||||
|
||||
except subprocess.CalledProcessError as e:
|
||||
Logger.print_error(
|
||||
f"Error creating service file {service_file_target}: {e}"
|
||||
)
|
||||
raise
|
||||
except OSError as e:
|
||||
Logger.print_error(f"Error writing file: {e}")
|
||||
raise
|
||||
|
||||
def delete(self, del_remnants: bool) -> None:
|
||||
service_file = self.get_service_file_name(extension=True)
|
||||
service_file_path = self.get_service_file_path()
|
||||
|
||||
Logger.print_status(f"Deleting Moonraker Instance: {service_file}")
|
||||
|
||||
try:
|
||||
command = ["sudo", "rm", "-f", service_file_path]
|
||||
subprocess.run(command, check=True)
|
||||
Logger.print_ok(f"Service file deleted: {service_file_path}")
|
||||
except subprocess.CalledProcessError as e:
|
||||
Logger.print_error(f"Error deleting service file: {e}")
|
||||
raise
|
||||
|
||||
if del_remnants:
|
||||
self._delete_moonraker_remnants()
|
||||
|
||||
def write_service_file(
|
||||
self, service_template_path: str, service_file_target: str, env_file_target: str
|
||||
):
|
||||
service_content = self._prep_service_file(
|
||||
service_template_path, env_file_target
|
||||
)
|
||||
command = ["sudo", "tee", service_file_target]
|
||||
subprocess.run(
|
||||
command,
|
||||
input=service_content.encode(),
|
||||
stdout=subprocess.DEVNULL,
|
||||
check=True,
|
||||
)
|
||||
Logger.print_ok(f"Service file created: {service_file_target}")
|
||||
|
||||
def write_env_file(self, env_template_file_path: str, env_file_target: str):
|
||||
env_file_content = self._prep_env_file(env_template_file_path)
|
||||
with open(env_file_target, "w") as env_file:
|
||||
env_file.write(env_file_content)
|
||||
Logger.print_ok(f"Env file created: {env_file_target}")
|
||||
|
||||
def _delete_moonraker_remnants(self) -> None:
|
||||
try:
|
||||
Logger.print_status(f"Delete {self.moonraker_dir} ...")
|
||||
shutil.rmtree(Path(self.moonraker_dir))
|
||||
Logger.print_status(f"Delete {self.env_dir} ...")
|
||||
shutil.rmtree(Path(self.env_dir))
|
||||
except FileNotFoundError:
|
||||
Logger.print_status("Cannot delete Moonraker directories. Not found.")
|
||||
except PermissionError as e:
|
||||
Logger.print_error(f"Error deleting Moonraker directories: {e}")
|
||||
raise
|
||||
|
||||
Logger.print_ok("Directories successfully deleted.")
|
||||
|
||||
def _prep_service_file(self, service_template_path, env_file_path):
|
||||
try:
|
||||
with open(service_template_path, "r") as template_file:
|
||||
template_content = template_file.read()
|
||||
except FileNotFoundError:
|
||||
Logger.print_error(
|
||||
f"Unable to open {service_template_path} - File not found"
|
||||
)
|
||||
raise
|
||||
service_content = template_content.replace("%USER%", self.user)
|
||||
service_content = service_content.replace("%MOONRAKER_DIR%", self.moonraker_dir)
|
||||
service_content = service_content.replace("%ENV%", self.env_dir)
|
||||
service_content = service_content.replace("%ENV_FILE%", env_file_path)
|
||||
return service_content
|
||||
|
||||
def _prep_env_file(self, env_template_file_path):
|
||||
try:
|
||||
with open(env_template_file_path, "r") as env_file:
|
||||
env_template_file_content = env_file.read()
|
||||
except FileNotFoundError:
|
||||
Logger.print_error(
|
||||
f"Unable to open {env_template_file_path} - File not found"
|
||||
)
|
||||
raise
|
||||
env_file_content = env_template_file_content.replace(
|
||||
"%MOONRAKER_DIR%", self.moonraker_dir
|
||||
)
|
||||
env_file_content = env_file_content.replace("%PRINTER_DATA%", self.data_dir)
|
||||
return env_file_content
|
||||
|
||||
def _get_cfg(self):
|
||||
cfg_file_loc = f"{self.cfg_dir}/moonraker.conf"
|
||||
if Path(cfg_file_loc).is_file():
|
||||
return cfg_file_loc
|
||||
return None
|
||||
|
||||
def _get_port(self) -> Union[int, None]:
|
||||
if self.cfg_file is None:
|
||||
return None
|
||||
|
||||
cm = ConfigManager(cfg_file=self.cfg_file)
|
||||
cm.read_config()
|
||||
port = cm.get_value("server", "port")
|
||||
|
||||
return int(port) if port is not None else port
|
||||
72
kiauh/modules/moonraker/moonraker_dialogs.py
Normal file
72
kiauh/modules/moonraker/moonraker_dialogs.py
Normal file
@@ -0,0 +1,72 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# ======================================================================= #
|
||||
# Copyright (C) 2020 - 2023 Dominik Willner <th33xitus@gmail.com> #
|
||||
# #
|
||||
# This file is part of KIAUH - Klipper Installation And Update Helper #
|
||||
# https://github.com/dw-0/kiauh #
|
||||
# #
|
||||
# This file may be distributed under the terms of the GNU GPLv3 license #
|
||||
# ======================================================================= #
|
||||
|
||||
import textwrap
|
||||
from typing import List
|
||||
|
||||
from kiauh.core.menus.base_menu import print_back_footer
|
||||
from kiauh.modules.klipper.klipper import Klipper
|
||||
from kiauh.modules.moonraker.moonraker import Moonraker
|
||||
from kiauh.utils.constants import COLOR_GREEN, RESET_FORMAT, COLOR_YELLOW, COLOR_CYAN
|
||||
|
||||
|
||||
def print_moonraker_overview(
|
||||
klipper_instances: List[Klipper],
|
||||
moonraker_instances: List[Moonraker],
|
||||
show_index=False,
|
||||
show_select_all=False,
|
||||
):
|
||||
headline = f"{COLOR_GREEN}The following instances were found:{RESET_FORMAT}"
|
||||
dialog = textwrap.dedent(
|
||||
f"""
|
||||
/=======================================================\\
|
||||
|{headline:^64}|
|
||||
|-------------------------------------------------------|
|
||||
"""
|
||||
)[1:]
|
||||
|
||||
if show_select_all:
|
||||
select_all = f"{COLOR_YELLOW}a) Select all{RESET_FORMAT}"
|
||||
dialog += f"| {select_all:<63}|\n"
|
||||
dialog += "| |\n"
|
||||
|
||||
instance_map = {
|
||||
k.get_service_file_name(): k.get_service_file_name().replace(
|
||||
"klipper", "moonraker"
|
||||
)
|
||||
if k.suffix in [m.suffix for m in moonraker_instances]
|
||||
else ""
|
||||
for k in klipper_instances
|
||||
}
|
||||
|
||||
for i, k in enumerate(instance_map):
|
||||
mr_name = instance_map.get(k)
|
||||
m = f"<-> {mr_name}" if mr_name != "" else ""
|
||||
line = f"{COLOR_CYAN}{f'{i})' if show_index else '●'} {k} {m} {RESET_FORMAT}"
|
||||
dialog += f"| {line:<63}|\n"
|
||||
|
||||
warn_l1 = f"{COLOR_YELLOW}PLEASE NOTE: {RESET_FORMAT}"
|
||||
warn_l2 = f"{COLOR_YELLOW}If you select an instance with an existing Moonraker{RESET_FORMAT}"
|
||||
warn_l3 = f"{COLOR_YELLOW}instance, that Moonraker instance will be re-created!{RESET_FORMAT}"
|
||||
warning = textwrap.dedent(
|
||||
f"""
|
||||
| |
|
||||
|-------------------------------------------------------|
|
||||
| {warn_l1:<63}|
|
||||
| {warn_l2:<63}|
|
||||
| {warn_l3:<63}|
|
||||
"""
|
||||
)[1:]
|
||||
|
||||
dialog += warning
|
||||
|
||||
print(dialog, end="")
|
||||
print_back_footer()
|
||||
317
kiauh/modules/moonraker/moonraker_setup.py
Normal file
317
kiauh/modules/moonraker/moonraker_setup.py
Normal file
@@ -0,0 +1,317 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# ======================================================================= #
|
||||
# Copyright (C) 2020 - 2023 Dominik Willner <th33xitus@gmail.com> #
|
||||
# #
|
||||
# This file is part of KIAUH - Klipper Installation And Update Helper #
|
||||
# https://github.com/dw-0/kiauh #
|
||||
# #
|
||||
# This file may be distributed under the terms of the GNU GPLv3 license #
|
||||
# ======================================================================= #
|
||||
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import List
|
||||
|
||||
from kiauh import KIAUH_CFG
|
||||
from kiauh.core.backup_manager.backup_manager import BackupManager
|
||||
from kiauh.core.config_manager.config_manager import ConfigManager
|
||||
from kiauh.core.instance_manager.instance_manager import InstanceManager
|
||||
from kiauh.modules.klipper.klipper import Klipper
|
||||
from kiauh.modules.klipper.klipper_dialogs import (
|
||||
print_instance_overview,
|
||||
print_update_warn_dialog,
|
||||
)
|
||||
from kiauh.core.repo_manager.repo_manager import RepoManager
|
||||
from kiauh.modules.moonraker import (
|
||||
EXIT_MOONRAKER_SETUP,
|
||||
DEFAULT_MOONRAKER_REPO_URL,
|
||||
MOONRAKER_DIR,
|
||||
MOONRAKER_ENV_DIR,
|
||||
MOONRAKER_REQUIREMENTS_TXT,
|
||||
POLKIT_LEGACY_FILE,
|
||||
POLKIT_FILE,
|
||||
POLKIT_USR_FILE,
|
||||
POLKIT_SCRIPT,
|
||||
)
|
||||
from kiauh.modules.moonraker.moonraker import Moonraker
|
||||
from kiauh.modules.moonraker.moonraker_dialogs import print_moonraker_overview
|
||||
from kiauh.modules.moonraker.moonraker_utils import create_example_moonraker_conf
|
||||
from kiauh.utils.filesystem_utils import check_file_exist
|
||||
from kiauh.utils.input_utils import (
|
||||
get_confirm,
|
||||
get_selection_input,
|
||||
)
|
||||
from kiauh.utils.logger import Logger
|
||||
from kiauh.utils.system_utils import (
|
||||
parse_packages_from_file,
|
||||
create_python_venv,
|
||||
install_python_requirements,
|
||||
update_system_package_lists,
|
||||
install_system_packages,
|
||||
)
|
||||
|
||||
|
||||
def run_moonraker_setup(install: bool) -> None:
|
||||
kl_im = InstanceManager(Klipper)
|
||||
kl_instance_list = kl_im.instances
|
||||
kl_instance_count = len(kl_instance_list)
|
||||
mr_im = InstanceManager(Moonraker)
|
||||
mr_instance_list = mr_im.instances
|
||||
mr_instance_count = len(mr_instance_list)
|
||||
|
||||
if not (sys.version_info.major >= 3 and sys.version_info.minor >= 7):
|
||||
Logger.print_error("Versioncheck failed!")
|
||||
Logger.print_error("Python 3.7 or newer required to run Moonraker.")
|
||||
return
|
||||
|
||||
is_klipper_installed = kl_instance_count > 0
|
||||
if install and not is_klipper_installed:
|
||||
Logger.print_warn("Klipper not installed!")
|
||||
Logger.print_warn("Moonraker cannot be installed! Install Klipper first.")
|
||||
return
|
||||
|
||||
is_moonraker_installed = mr_instance_count > 0
|
||||
if not install and not is_moonraker_installed:
|
||||
Logger.print_warn("Moonraker not installed!")
|
||||
return
|
||||
|
||||
if install:
|
||||
install_moonraker(mr_im, mr_instance_list, kl_instance_list)
|
||||
|
||||
if not install:
|
||||
remove_moonraker(mr_im, mr_instance_list)
|
||||
|
||||
|
||||
def handle_existing_instances(instance_list: List[Klipper]) -> bool:
|
||||
instance_count = len(instance_list)
|
||||
|
||||
if instance_count > 0:
|
||||
print_instance_overview(instance_list)
|
||||
if not get_confirm("Add new instances?", allow_go_back=True):
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def install_moonraker(
|
||||
instance_manager: InstanceManager,
|
||||
moonraker_instances: List[Moonraker],
|
||||
klipper_instances: List[Klipper],
|
||||
) -> None:
|
||||
selected_klipper_instance = 0
|
||||
if len(klipper_instances) > 1:
|
||||
print_moonraker_overview(
|
||||
klipper_instances,
|
||||
moonraker_instances,
|
||||
show_index=True,
|
||||
show_select_all=True,
|
||||
)
|
||||
options = [str(i) for i in range(len(klipper_instances))]
|
||||
options.extend(["a", "A", "b", "B"])
|
||||
question = "Select Klipper instance to setup Moonraker for"
|
||||
selected_klipper_instance = get_selection_input(question, options).lower()
|
||||
|
||||
instance_names = []
|
||||
if selected_klipper_instance == "b":
|
||||
Logger.print_status(EXIT_MOONRAKER_SETUP)
|
||||
return
|
||||
|
||||
elif selected_klipper_instance == "a":
|
||||
for instance in klipper_instances:
|
||||
instance_names.append(instance.suffix)
|
||||
|
||||
else:
|
||||
index = int(selected_klipper_instance)
|
||||
instance_names.append(klipper_instances[index].suffix)
|
||||
|
||||
create_example_cfg = get_confirm("Create example moonraker.conf?")
|
||||
setup_moonraker_prerequesites()
|
||||
install_moonraker_polkit()
|
||||
|
||||
used_ports_map = {
|
||||
instance.suffix: instance.port for instance in moonraker_instances
|
||||
}
|
||||
for name in instance_names:
|
||||
current_instance = Moonraker(suffix=name)
|
||||
|
||||
instance_manager.current_instance = current_instance
|
||||
instance_manager.create_instance()
|
||||
instance_manager.enable_instance()
|
||||
|
||||
if create_example_cfg:
|
||||
create_example_moonraker_conf(current_instance, used_ports_map)
|
||||
|
||||
instance_manager.start_instance()
|
||||
|
||||
instance_manager.reload_daemon()
|
||||
|
||||
|
||||
def setup_moonraker_prerequesites() -> None:
|
||||
cm = ConfigManager(cfg_file=KIAUH_CFG)
|
||||
cm.read_config()
|
||||
|
||||
repo = str(
|
||||
cm.get_value("moonraker", "repository_url") or DEFAULT_MOONRAKER_REPO_URL
|
||||
)
|
||||
branch = str(cm.get_value("moonraker", "branch") or "master")
|
||||
|
||||
repo_manager = RepoManager(
|
||||
repo=repo,
|
||||
branch=branch,
|
||||
target_dir=MOONRAKER_DIR,
|
||||
)
|
||||
repo_manager.clone_repo()
|
||||
|
||||
# install moonraker dependencies and create python virtualenv
|
||||
install_moonraker_packages(Path(MOONRAKER_DIR))
|
||||
create_python_venv(Path(MOONRAKER_ENV_DIR))
|
||||
moonraker_py_req = Path(MOONRAKER_REQUIREMENTS_TXT)
|
||||
install_python_requirements(Path(MOONRAKER_ENV_DIR), moonraker_py_req)
|
||||
|
||||
|
||||
def install_moonraker_packages(moonraker_dir: Path) -> None:
|
||||
script = Path(f"{moonraker_dir}/scripts/install-moonraker.sh")
|
||||
packages = parse_packages_from_file(script)
|
||||
update_system_package_lists(silent=False)
|
||||
install_system_packages(packages)
|
||||
|
||||
|
||||
def install_moonraker_polkit() -> None:
|
||||
Logger.print_status("Installing Moonraker policykit rules ...")
|
||||
|
||||
legacy_file_exists = check_file_exist(Path(POLKIT_LEGACY_FILE), True)
|
||||
polkit_file_exists = check_file_exist(Path(POLKIT_FILE), True)
|
||||
usr_file_exists = check_file_exist(Path(POLKIT_USR_FILE), True)
|
||||
|
||||
if legacy_file_exists or (polkit_file_exists and usr_file_exists):
|
||||
Logger.print_info("Moonraker policykit rules are already installed.")
|
||||
return
|
||||
|
||||
try:
|
||||
command = [POLKIT_SCRIPT, "--disable-systemctl"]
|
||||
result = subprocess.run(
|
||||
command, stderr=subprocess.PIPE, stdout=subprocess.DEVNULL, text=True
|
||||
)
|
||||
if result.returncode != 0 or result.stderr:
|
||||
Logger.print_error(f"{result.stderr}", False)
|
||||
Logger.print_error("Installing Moonraker policykit rules failed!")
|
||||
return
|
||||
|
||||
Logger.print_ok("Moonraker policykit rules successfully installed!")
|
||||
except subprocess.CalledProcessError as e:
|
||||
log = f"Error while installing Moonraker policykit rules: {e.stderr.decode()}"
|
||||
Logger.print_error(log)
|
||||
|
||||
|
||||
def remove_moonraker(
|
||||
instance_manager: InstanceManager, instance_list: List[Moonraker]
|
||||
) -> None:
|
||||
print_instance_overview(instance_list, True, True)
|
||||
|
||||
options = [str(i) for i in range(len(instance_list))]
|
||||
options.extend(["a", "A", "b", "B"])
|
||||
|
||||
selection = get_selection_input("Select Moonraker instance to remove", options)
|
||||
|
||||
del_remnants = False
|
||||
remove_polkit = False
|
||||
instances_to_remove = []
|
||||
if selection == "b".lower():
|
||||
return
|
||||
elif selection == "a".lower():
|
||||
question = f"Delete {MOONRAKER_DIR} and {MOONRAKER_ENV_DIR}?"
|
||||
del_remnants = get_confirm(question, False, True)
|
||||
instances_to_remove.extend(instance_list)
|
||||
remove_polkit = True
|
||||
Logger.print_status("Removing all Moonraker instances ...")
|
||||
else:
|
||||
instance = instance_list[int(selection)]
|
||||
instance_name = instance.get_service_file_name()
|
||||
instances_to_remove.append(instance)
|
||||
is_last_instance = len(instance_list) == 1
|
||||
if is_last_instance:
|
||||
question = f"Delete {MOONRAKER_DIR} and {MOONRAKER_ENV_DIR}?"
|
||||
del_remnants = get_confirm(question, False, True)
|
||||
remove_polkit = True
|
||||
Logger.print_status(f"Removing Moonraker instance {instance_name} ...")
|
||||
|
||||
if del_remnants is None:
|
||||
Logger.print_status("Exiting Moonraker Uninstaller ...")
|
||||
return
|
||||
|
||||
remove_instances(
|
||||
instance_manager,
|
||||
instances_to_remove,
|
||||
remove_polkit,
|
||||
del_remnants,
|
||||
)
|
||||
|
||||
|
||||
def remove_instances(
|
||||
instance_manager: InstanceManager,
|
||||
instance_list: List[Moonraker],
|
||||
remove_polkit: bool,
|
||||
del_remnants: bool,
|
||||
) -> None:
|
||||
for instance in instance_list:
|
||||
instance_manager.current_instance = instance
|
||||
instance_manager.stop_instance()
|
||||
instance_manager.disable_instance()
|
||||
instance_manager.delete_instance(del_remnants=del_remnants)
|
||||
|
||||
if remove_polkit:
|
||||
remove_polkit_rules()
|
||||
|
||||
instance_manager.reload_daemon()
|
||||
|
||||
|
||||
def remove_polkit_rules() -> None:
|
||||
Logger.print_status("Removing all Moonraker policykit rules ...")
|
||||
if not Path(MOONRAKER_DIR).exists():
|
||||
log = "Cannot remove policykit rules. Moonraker directory not found."
|
||||
Logger.print_warn(log)
|
||||
return
|
||||
|
||||
try:
|
||||
command = [f"{MOONRAKER_DIR}/scripts/set-policykit-rules.sh", "--clear"]
|
||||
subprocess.run(
|
||||
command, stderr=subprocess.PIPE, stdout=subprocess.DEVNULL, check=True
|
||||
)
|
||||
except subprocess.CalledProcessError as e:
|
||||
Logger.print_error(f"Error while removing policykit rules: {e}")
|
||||
|
||||
Logger.print_ok("Policykit rules successfully removed!")
|
||||
|
||||
|
||||
def update_moonraker() -> None:
|
||||
print_update_warn_dialog()
|
||||
if not get_confirm("Update Moonraker now?"):
|
||||
return
|
||||
|
||||
cm = ConfigManager(cfg_file=KIAUH_CFG)
|
||||
cm.read_config()
|
||||
|
||||
if cm.get_value("kiauh", "backup_before_update"):
|
||||
backup_manager = BackupManager(source=MOONRAKER_DIR, backup_name="moonraker")
|
||||
backup_manager.backup()
|
||||
backup_manager.backup_name = "moonraker-env"
|
||||
backup_manager.source = MOONRAKER_ENV_DIR
|
||||
backup_manager.backup()
|
||||
|
||||
instance_manager = InstanceManager(Moonraker)
|
||||
instance_manager.stop_all_instance()
|
||||
|
||||
repo = str(
|
||||
cm.get_value("moonraker", "repository_url") or DEFAULT_MOONRAKER_REPO_URL
|
||||
)
|
||||
branch = str(cm.get_value("moonraker", "branch") or "master")
|
||||
|
||||
repo_manager = RepoManager(
|
||||
repo=repo,
|
||||
branch=branch,
|
||||
target_dir=MOONRAKER_DIR,
|
||||
)
|
||||
repo_manager.pull_repo()
|
||||
instance_manager.start_all_instance()
|
||||
75
kiauh/modules/moonraker/moonraker_utils.py
Normal file
75
kiauh/modules/moonraker/moonraker_utils.py
Normal file
@@ -0,0 +1,75 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# ======================================================================= #
|
||||
# Copyright (C) 2020 - 2023 Dominik Willner <th33xitus@gmail.com> #
|
||||
# #
|
||||
# This file is part of KIAUH - Klipper Installation And Update Helper #
|
||||
# https://github.com/dw-0/kiauh #
|
||||
# #
|
||||
# This file may be distributed under the terms of the GNU GPLv3 license #
|
||||
# ======================================================================= #
|
||||
|
||||
import os
|
||||
import shutil
|
||||
from typing import List, Dict
|
||||
|
||||
from kiauh.core.config_manager.config_manager import ConfigManager
|
||||
from kiauh.modules.moonraker import (
|
||||
DEFAULT_MOONRAKER_PORT,
|
||||
MODULE_PATH,
|
||||
)
|
||||
from kiauh.modules.moonraker.moonraker import Moonraker
|
||||
from kiauh.utils.logger import Logger
|
||||
from kiauh.utils.system_utils import (
|
||||
get_ipv4_addr,
|
||||
)
|
||||
|
||||
|
||||
def create_example_moonraker_conf(
|
||||
instance: Moonraker, ports_map: Dict[str, int]
|
||||
) -> None:
|
||||
Logger.print_status(f"Creating example moonraker.conf in '{instance.cfg_dir}'")
|
||||
if instance.cfg_file is not None:
|
||||
Logger.print_info(f"moonraker.conf in '{instance.cfg_dir}' already exists.")
|
||||
return
|
||||
|
||||
source = os.path.join(MODULE_PATH, "res", "moonraker.conf")
|
||||
target = os.path.join(instance.cfg_dir, "moonraker.conf")
|
||||
try:
|
||||
shutil.copy(source, target)
|
||||
except OSError as e:
|
||||
Logger.print_error(f"Unable to create example moonraker.conf:\n{e}")
|
||||
return
|
||||
|
||||
cm = ConfigManager(target)
|
||||
cm.read_config()
|
||||
|
||||
ports = [
|
||||
ports_map.get(instance)
|
||||
for instance in ports_map
|
||||
if ports_map.get(instance) is not None
|
||||
]
|
||||
if ports_map.get(instance.suffix) is None:
|
||||
# this could be improved to not increment the max value of the ports list and assign it as the port
|
||||
# as it can lead to situation where the port for e.g. instance moonraker-2 becomes 7128 if the port
|
||||
# of moonraker-1 is 7125 and moonraker-3 is 7127 and there are moonraker.conf files for moonraker-1
|
||||
# and moonraker-3 already. though, there does not seem to be a very reliable way of always assigning
|
||||
# the correct port to each instance and the user will likely be required to correct the value manually.
|
||||
port = max(ports) + 1 if ports else DEFAULT_MOONRAKER_PORT
|
||||
else:
|
||||
port = ports_map.get(instance.suffix)
|
||||
|
||||
ports_map[instance.suffix] = port
|
||||
|
||||
uds = f"{instance.comms_dir}/klippy.sock"
|
||||
ip = get_ipv4_addr().split(".")[:2]
|
||||
ip.extend(["0", "0/16"])
|
||||
trusted_clients = f"\n{'.'.join(ip)}"
|
||||
trusted_clients += cm.get_value("authorization", "trusted_clients")
|
||||
|
||||
cm.set_value("server", "port", str(port))
|
||||
cm.set_value("server", "klippy_uds_address", uds)
|
||||
cm.set_value("authorization", "trusted_clients", trusted_clients)
|
||||
|
||||
cm.write_config()
|
||||
Logger.print_ok(f"Example moonraker.conf created in '{instance.cfg_dir}'")
|
||||
29
kiauh/modules/moonraker/res/moonraker.conf
Normal file
29
kiauh/modules/moonraker/res/moonraker.conf
Normal file
@@ -0,0 +1,29 @@
|
||||
[server]
|
||||
host: 0.0.0.0
|
||||
port: %PORT%
|
||||
klippy_uds_address: %UDS%
|
||||
|
||||
[authorization]
|
||||
trusted_clients:
|
||||
10.0.0.0/8
|
||||
127.0.0.0/8
|
||||
169.254.0.0/16
|
||||
172.16.0.0/12
|
||||
192.168.0.0/16
|
||||
FE80::/10
|
||||
::1/128
|
||||
cors_domains:
|
||||
*.lan
|
||||
*.local
|
||||
*://localhost
|
||||
*://localhost:*
|
||||
*://my.mainsail.xyz
|
||||
*://app.fluidd.xyz
|
||||
|
||||
[octoprint_compat]
|
||||
|
||||
[history]
|
||||
|
||||
[update_manager]
|
||||
channel: dev
|
||||
refresh_interval: 168
|
||||
1
kiauh/modules/moonraker/res/moonraker.env
Normal file
1
kiauh/modules/moonraker/res/moonraker.env
Normal file
@@ -0,0 +1 @@
|
||||
MOONRAKER_ARGS="%MOONRAKER_DIR%/moonraker/moonraker.py -d %PRINTER_DATA%"
|
||||
19
kiauh/modules/moonraker/res/moonraker.service
Normal file
19
kiauh/modules/moonraker/res/moonraker.service
Normal file
@@ -0,0 +1,19 @@
|
||||
[Unit]
|
||||
Description=API Server for Klipper SV1
|
||||
Documentation=https://moonraker.readthedocs.io/
|
||||
Requires=network-online.target
|
||||
After=network-online.target
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=%USER%
|
||||
SupplementaryGroups=moonraker-admin
|
||||
RemainAfterExit=yes
|
||||
WorkingDirectory=%MOONRAKER_DIR%
|
||||
EnvironmentFile=%ENV_FILE%
|
||||
ExecStart=%ENV%/bin/python $MOONRAKER_ARGS
|
||||
Restart=always
|
||||
RestartSec=10
|
||||
19
kiauh/utils/__init__.py
Normal file
19
kiauh/utils/__init__.py
Normal file
@@ -0,0 +1,19 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# ======================================================================= #
|
||||
# Copyright (C) 2020 - 2023 Dominik Willner <th33xitus@gmail.com> #
|
||||
# #
|
||||
# This file is part of KIAUH - Klipper Installation And Update Helper #
|
||||
# https://github.com/dw-0/kiauh #
|
||||
# #
|
||||
# This file may be distributed under the terms of the GNU GPLv3 license #
|
||||
# ======================================================================= #
|
||||
import os
|
||||
|
||||
MODULE_PATH = os.path.dirname(os.path.abspath(__file__))
|
||||
INVALID_CHOICE = "Invalid choice. Please select a valid value."
|
||||
|
||||
# ================== NGINX =====================#
|
||||
NGINX_SITES_AVAILABLE = "/etc/nginx/sites-available"
|
||||
NGINX_SITES_ENABLED = "/etc/nginx/sites-enabled"
|
||||
NGINX_CONFD = "/etc/nginx/conf.d"
|
||||
45
kiauh/utils/common.py
Normal file
45
kiauh/utils/common.py
Normal file
@@ -0,0 +1,45 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# ======================================================================= #
|
||||
# Copyright (C) 2020 - 2023 Dominik Willner <th33xitus@gmail.com> #
|
||||
# #
|
||||
# This file is part of KIAUH - Klipper Installation And Update Helper #
|
||||
# https://github.com/dw-0/kiauh #
|
||||
# #
|
||||
# This file may be distributed under the terms of the GNU GPLv3 license #
|
||||
# ======================================================================= #
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Dict, Literal, List
|
||||
|
||||
from kiauh.utils.constants import COLOR_CYAN, RESET_FORMAT
|
||||
from kiauh.utils.logger import Logger
|
||||
from kiauh.utils.system_utils import check_package_install, install_system_packages
|
||||
|
||||
|
||||
def get_current_date() -> Dict[Literal["date", "time"], str]:
|
||||
"""
|
||||
Get the current date |
|
||||
:return: Dict holding a date and time key:value pair
|
||||
"""
|
||||
now: datetime = datetime.today()
|
||||
date: str = now.strftime("%Y-%m-%d")
|
||||
time: str = now.strftime("%H-%M-%S")
|
||||
|
||||
return {"date": date, "time": time}
|
||||
|
||||
|
||||
def check_install_dependencies(deps: List[str]) -> None:
|
||||
"""
|
||||
Common helper method to check if dependencies are installed
|
||||
and if not, install them automatically |
|
||||
:param deps: List of strings of package names to check if installed
|
||||
:return: None
|
||||
"""
|
||||
requirements = check_package_install(deps)
|
||||
if requirements:
|
||||
Logger.print_status("Installing dependencies ...")
|
||||
Logger.print_info("The following packages need installation:")
|
||||
for _ in requirements:
|
||||
print(f"{COLOR_CYAN}● {_}{RESET_FORMAT}")
|
||||
install_system_packages(requirements)
|
||||
25
kiauh/utils/constants.py
Normal file
25
kiauh/utils/constants.py
Normal file
@@ -0,0 +1,25 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# ======================================================================= #
|
||||
# Copyright (C) 2020 - 2023 Dominik Willner <th33xitus@gmail.com> #
|
||||
# #
|
||||
# This file is part of KIAUH - Klipper Installation And Update Helper #
|
||||
# https://github.com/dw-0/kiauh #
|
||||
# #
|
||||
# This file may be distributed under the terms of the GNU GPLv3 license #
|
||||
# ======================================================================= #
|
||||
|
||||
import os
|
||||
import pwd
|
||||
|
||||
# text colors and formats
|
||||
COLOR_WHITE = "\033[37m" # white
|
||||
COLOR_MAGENTA = "\033[35m" # magenta
|
||||
COLOR_GREEN = "\033[92m" # bright green
|
||||
COLOR_YELLOW = "\033[93m" # bright yellow
|
||||
COLOR_RED = "\033[91m" # bright red
|
||||
COLOR_CYAN = "\033[96m" # bright cyan
|
||||
RESET_FORMAT = "\033[0m" # reset format
|
||||
# current user
|
||||
CURRENT_USER = pwd.getpwuid(os.getuid())[0]
|
||||
SYSTEMD = "/etc/systemd/system"
|
||||
151
kiauh/utils/filesystem_utils.py
Normal file
151
kiauh/utils/filesystem_utils.py
Normal file
@@ -0,0 +1,151 @@
|
||||
#!/usr/bin/env python3
|
||||
# ======================================================================= #
|
||||
# Copyright (C) 2020 - 2023 Dominik Willner <th33xitus@gmail.com> #
|
||||
# #
|
||||
# This file is part of KIAUH - Klipper Installation And Update Helper #
|
||||
# https://github.com/dw-0/kiauh #
|
||||
# #
|
||||
# This file may be distributed under the terms of the GNU GPLv3 license #
|
||||
# ======================================================================= #
|
||||
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
from zipfile import ZipFile
|
||||
|
||||
from kiauh.utils import (
|
||||
NGINX_SITES_AVAILABLE,
|
||||
MODULE_PATH,
|
||||
NGINX_CONFD,
|
||||
)
|
||||
from kiauh.utils.logger import Logger
|
||||
|
||||
|
||||
def check_file_exist(file_path: Path, sudo=False) -> bool:
|
||||
"""
|
||||
Helper function for checking the existence of a file |
|
||||
:param file_path: the absolute path of the file to check
|
||||
:param sudo: use sudo if required
|
||||
:return: True, if file exists, otherwise False
|
||||
"""
|
||||
if sudo:
|
||||
try:
|
||||
command = ["sudo", "find", file_path]
|
||||
subprocess.check_output(command, stderr=subprocess.DEVNULL)
|
||||
return True
|
||||
except subprocess.CalledProcessError:
|
||||
return False
|
||||
else:
|
||||
if Path(file_path).exists():
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
def create_directory(_dir: Path) -> None:
|
||||
"""
|
||||
Helper function for creating a directory or skipping if it already exists |
|
||||
:param _dir: the directory to create
|
||||
:return: None
|
||||
"""
|
||||
try:
|
||||
if not os.path.isdir(_dir):
|
||||
os.makedirs(_dir, exist_ok=True)
|
||||
Logger.print_ok(f"Created directory: {_dir}")
|
||||
except OSError as e:
|
||||
Logger.print_error(f"Error creating folder: {e}")
|
||||
raise
|
||||
|
||||
|
||||
def create_symlink(source: Path, target: Path, sudo=False) -> None:
|
||||
try:
|
||||
cmd = ["ln", "-sf", source, target]
|
||||
if sudo:
|
||||
cmd.insert(0, "sudo")
|
||||
subprocess.run(cmd, stderr=subprocess.PIPE, check=True)
|
||||
except subprocess.CalledProcessError as e:
|
||||
Logger.print_error(f"Failed to create symlink: {e}")
|
||||
raise
|
||||
|
||||
|
||||
def remove_file(file_path: Path, sudo=False) -> None:
|
||||
try:
|
||||
cmd = f"{'sudo ' if sudo else ''}rm -f {file_path}"
|
||||
subprocess.run(cmd, stderr=subprocess.PIPE, check=True, shell=True)
|
||||
except subprocess.CalledProcessError as e:
|
||||
log = f"Cannot remove file {file_path}: {e.stderr.decode()}"
|
||||
Logger.print_error(log)
|
||||
raise
|
||||
|
||||
|
||||
def unzip(file: str, target_dir: str) -> None:
|
||||
"""
|
||||
Helper function to unzip a zip-archive into a target directory |
|
||||
:param file: the zip-file to unzip
|
||||
:param target_dir: the target directory to extract the files into
|
||||
:return: None
|
||||
"""
|
||||
with ZipFile(file, "r") as _zip:
|
||||
_zip.extractall(target_dir)
|
||||
|
||||
|
||||
def copy_upstream_nginx_cfg() -> None:
|
||||
"""
|
||||
Creates an upstream.conf in /etc/nginx/conf.d
|
||||
:return: None
|
||||
"""
|
||||
source = os.path.join(MODULE_PATH, "res", "upstreams.conf")
|
||||
target = os.path.join(NGINX_CONFD, "upstreams.conf")
|
||||
try:
|
||||
command = ["sudo", "cp", source, target]
|
||||
subprocess.run(command, stderr=subprocess.PIPE, check=True)
|
||||
except subprocess.CalledProcessError as e:
|
||||
log = f"Unable to create upstreams.conf: {e.stderr.decode()}"
|
||||
Logger.print_error(log)
|
||||
raise
|
||||
|
||||
|
||||
def copy_common_vars_nginx_cfg() -> None:
|
||||
"""
|
||||
Creates a common_vars.conf in /etc/nginx/conf.d
|
||||
:return: None
|
||||
"""
|
||||
source = os.path.join(MODULE_PATH, "res", "common_vars.conf")
|
||||
target = os.path.join(NGINX_CONFD, "common_vars.conf")
|
||||
try:
|
||||
command = ["sudo", "cp", source, target]
|
||||
subprocess.run(command, stderr=subprocess.PIPE, check=True)
|
||||
except subprocess.CalledProcessError as e:
|
||||
log = f"Unable to create upstreams.conf: {e.stderr.decode()}"
|
||||
Logger.print_error(log)
|
||||
raise
|
||||
|
||||
|
||||
def create_nginx_cfg(name: str, port: int, root_dir: str) -> None:
|
||||
"""
|
||||
Creates an NGINX config from a template file and replaces all placeholders
|
||||
:param name: name of the config to create
|
||||
:param port: listen port
|
||||
:param root_dir: directory of the static files
|
||||
:return: None
|
||||
"""
|
||||
tmp = f"{Path.home()}/{name}.tmp"
|
||||
shutil.copy(os.path.join(MODULE_PATH, "res", "nginx_cfg"), tmp)
|
||||
with open(tmp, "r+") as f:
|
||||
content = f.read()
|
||||
content = content.replace("%NAME%", name)
|
||||
content = content.replace("%PORT%", str(port))
|
||||
content = content.replace("%ROOT_DIR%", root_dir)
|
||||
f.seek(0)
|
||||
f.write(content)
|
||||
f.truncate()
|
||||
|
||||
target = os.path.join(NGINX_SITES_AVAILABLE, name)
|
||||
try:
|
||||
command = ["sudo", "mv", tmp, target]
|
||||
subprocess.run(command, stderr=subprocess.PIPE, check=True)
|
||||
except subprocess.CalledProcessError as e:
|
||||
log = f"Unable to create '{target}': {e.stderr.decode()}"
|
||||
Logger.print_error(log)
|
||||
raise
|
||||
148
kiauh/utils/input_utils.py
Normal file
148
kiauh/utils/input_utils.py
Normal file
@@ -0,0 +1,148 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# ======================================================================= #
|
||||
# Copyright (C) 2020 - 2023 Dominik Willner <th33xitus@gmail.com> #
|
||||
# #
|
||||
# This file is part of KIAUH - Klipper Installation And Update Helper #
|
||||
# https://github.com/dw-0/kiauh #
|
||||
# #
|
||||
# This file may be distributed under the terms of the GNU GPLv3 license #
|
||||
# ======================================================================= #
|
||||
|
||||
from typing import Optional, List, Union
|
||||
|
||||
from kiauh.utils import INVALID_CHOICE
|
||||
from kiauh.utils.constants import COLOR_CYAN, RESET_FORMAT
|
||||
from kiauh.utils.logger import Logger
|
||||
|
||||
|
||||
def get_confirm(
|
||||
question: str, default_choice=True, allow_go_back=False
|
||||
) -> Union[bool, None]:
|
||||
"""
|
||||
Helper method for validating confirmation (yes/no) user input. |
|
||||
:param question: The question to display
|
||||
:param default_choice: A default if input was submitted without input
|
||||
:param allow_go_back: Navigate back to a previous dialog
|
||||
:return: Either True or False, or None on go_back
|
||||
"""
|
||||
options_confirm = ["y", "yes"]
|
||||
options_decline = ["n", "no"]
|
||||
options_go_back = ["b", "B"]
|
||||
|
||||
if default_choice:
|
||||
def_choice = "(Y/n)"
|
||||
options_confirm.append("")
|
||||
else:
|
||||
def_choice = "(y/N)"
|
||||
options_decline.append("")
|
||||
|
||||
while True:
|
||||
choice = (
|
||||
input(format_question(question + f" {def_choice}", None)).strip().lower()
|
||||
)
|
||||
|
||||
if choice in options_confirm:
|
||||
return True
|
||||
elif choice in options_decline:
|
||||
return False
|
||||
elif allow_go_back and choice in options_go_back:
|
||||
return None
|
||||
else:
|
||||
Logger.print_error(INVALID_CHOICE)
|
||||
|
||||
|
||||
def get_number_input(
|
||||
question: str, min_count: int, max_count=None, default=None, allow_go_back=False
|
||||
) -> Union[int, None]:
|
||||
"""
|
||||
Helper method to get a number input from the user
|
||||
:param question: The question to display
|
||||
:param min_count: The lowest allowed value
|
||||
:param max_count: The highest allowed value (or None)
|
||||
:param default: Optional default value
|
||||
:param allow_go_back: Navigate back to a previous dialog
|
||||
:return: Either the validated number input, or None on go_back
|
||||
"""
|
||||
options_go_back = ["b", "B"]
|
||||
_question = format_question(question, default)
|
||||
while True:
|
||||
_input = input(_question)
|
||||
if allow_go_back and _input in options_go_back:
|
||||
return None
|
||||
|
||||
if _input == "":
|
||||
return default
|
||||
|
||||
try:
|
||||
return validate_number_input(_input, min_count, max_count)
|
||||
except ValueError:
|
||||
Logger.print_error(INVALID_CHOICE)
|
||||
|
||||
|
||||
def get_string_input(question: str, exclude=Optional[List], default=None) -> str:
|
||||
"""
|
||||
Helper method to get a string input from the user
|
||||
:param question: The question to display
|
||||
:param exclude: List of strings which are not allowed
|
||||
:param default: Optional default value
|
||||
:return: The validated string value
|
||||
"""
|
||||
while True:
|
||||
_input = input(format_question(question, default)).strip()
|
||||
|
||||
if _input.isalnum() and _input.lower() not in exclude:
|
||||
return _input
|
||||
|
||||
Logger.print_error(INVALID_CHOICE)
|
||||
if _input in exclude:
|
||||
Logger.print_error("This value is already in use/reserved.")
|
||||
|
||||
|
||||
def get_selection_input(question: str, option_list: List, default=None) -> str:
|
||||
"""
|
||||
Helper method to get a selection from a list of options from the user
|
||||
:param question: The question to display
|
||||
:param option_list: The list of options the user can select from
|
||||
:param default: Optional default value
|
||||
:return: The option that was selected by the user
|
||||
"""
|
||||
while True:
|
||||
_input = input(format_question(question, default)).strip()
|
||||
|
||||
if _input in option_list:
|
||||
return _input
|
||||
|
||||
Logger.print_error(INVALID_CHOICE)
|
||||
|
||||
|
||||
def format_question(question: str, default=None) -> str:
|
||||
"""
|
||||
Helper method to have a standardized formatting of questions |
|
||||
:param question: The question to display
|
||||
:param default: If defined, the default option will be displayed to the user
|
||||
:return: The formatted question string
|
||||
"""
|
||||
formatted_q = question
|
||||
if default is not None:
|
||||
formatted_q += f" (default={default})"
|
||||
|
||||
return f"{COLOR_CYAN}###### {formatted_q}: {RESET_FORMAT}"
|
||||
|
||||
|
||||
def validate_number_input(value: str, min_count: int, max_count: int) -> int:
|
||||
"""
|
||||
Helper method for a simple number input validation. |
|
||||
:param value: The value to validate
|
||||
:param min_count: The lowest allowed value
|
||||
:param max_count: The highest allowed value (or None)
|
||||
:return: The validated value as Integer
|
||||
:raises: ValueError if value is invalid
|
||||
"""
|
||||
if max_count is not None:
|
||||
if min_count <= int(value) <= max_count:
|
||||
return int(value)
|
||||
elif int(value) >= min_count:
|
||||
return int(value)
|
||||
|
||||
raise ValueError
|
||||
61
kiauh/utils/logger.py
Normal file
61
kiauh/utils/logger.py
Normal file
@@ -0,0 +1,61 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# ======================================================================= #
|
||||
# Copyright (C) 2020 - 2023 Dominik Willner <th33xitus@gmail.com> #
|
||||
# #
|
||||
# This file is part of KIAUH - Klipper Installation And Update Helper #
|
||||
# https://github.com/dw-0/kiauh #
|
||||
# #
|
||||
# This file may be distributed under the terms of the GNU GPLv3 license #
|
||||
# ======================================================================= #
|
||||
|
||||
from kiauh.utils.constants import (
|
||||
COLOR_WHITE,
|
||||
COLOR_GREEN,
|
||||
COLOR_YELLOW,
|
||||
COLOR_RED,
|
||||
COLOR_MAGENTA,
|
||||
RESET_FORMAT,
|
||||
)
|
||||
|
||||
|
||||
class Logger:
|
||||
@staticmethod
|
||||
def info(msg):
|
||||
# log to kiauh.log
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def warn(msg):
|
||||
# log to kiauh.log
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def error(msg):
|
||||
# log to kiauh.log
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def print_info(msg, prefix=True, start="", end="\n") -> None:
|
||||
message = f"[INFO] {msg}" if prefix else msg
|
||||
print(f"{COLOR_WHITE}{start}{message}{RESET_FORMAT}", end=end)
|
||||
|
||||
@staticmethod
|
||||
def print_ok(msg, prefix=True, start="", end="\n") -> None:
|
||||
message = f"[OK] {msg}" if prefix else msg
|
||||
print(f"{COLOR_GREEN}{start}{message}{RESET_FORMAT}", end=end)
|
||||
|
||||
@staticmethod
|
||||
def print_warn(msg, prefix=True, start="", end="\n") -> None:
|
||||
message = f"[WARN] {msg}" if prefix else msg
|
||||
print(f"{COLOR_YELLOW}{start}{message}{RESET_FORMAT}", end=end)
|
||||
|
||||
@staticmethod
|
||||
def print_error(msg, prefix=True, start="", end="\n") -> None:
|
||||
message = f"[ERROR] {msg}" if prefix else msg
|
||||
print(f"{COLOR_RED}{start}{message}{RESET_FORMAT}", end=end)
|
||||
|
||||
@staticmethod
|
||||
def print_status(msg, prefix=True, start="", end="\n") -> None:
|
||||
message = f"\n###### {msg}" if prefix else msg
|
||||
print(f"{COLOR_MAGENTA}{start}{message}{RESET_FORMAT}", end=end)
|
||||
6
kiauh/utils/res/common_vars.conf
Normal file
6
kiauh/utils/res/common_vars.conf
Normal file
@@ -0,0 +1,6 @@
|
||||
# /etc/nginx/conf.d/common_vars.conf
|
||||
|
||||
map $http_upgrade $connection_upgrade {
|
||||
default upgrade;
|
||||
'' close;
|
||||
}
|
||||
96
kiauh/utils/res/nginx_cfg
Normal file
96
kiauh/utils/res/nginx_cfg
Normal file
@@ -0,0 +1,96 @@
|
||||
# /etc/nginx/sites-available/%NAME%
|
||||
|
||||
server {
|
||||
listen %PORT%;
|
||||
|
||||
access_log /var/log/nginx/%NAME%-access.log;
|
||||
error_log /var/log/nginx/%NAME%-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/javascript application/x-javascript application/json application/xml;
|
||||
|
||||
# web_path from %NAME% static files
|
||||
root %ROOT_DIR%;
|
||||
|
||||
index index.html;
|
||||
server_name _;
|
||||
|
||||
# disable max upload size checks
|
||||
client_max_body_size 0;
|
||||
|
||||
# disable proxy request buffering
|
||||
proxy_request_buffering off;
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
|
||||
location = /index.html {
|
||||
add_header Cache-Control "no-store, no-cache, must-revalidate";
|
||||
}
|
||||
|
||||
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 ~ ^/(printer|api|access|machine|server)/ {
|
||||
proxy_pass http://apiserver$request_uri;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_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_set_header X-Scheme $scheme;
|
||||
proxy_read_timeout 600;
|
||||
}
|
||||
|
||||
location /webcam/ {
|
||||
postpone_output 0;
|
||||
proxy_buffering off;
|
||||
proxy_ignore_headers X-Accel-Buffering;
|
||||
access_log off;
|
||||
error_log off;
|
||||
proxy_pass http://mjpgstreamer1/;
|
||||
}
|
||||
|
||||
location /webcam2/ {
|
||||
postpone_output 0;
|
||||
proxy_buffering off;
|
||||
proxy_ignore_headers X-Accel-Buffering;
|
||||
access_log off;
|
||||
error_log off;
|
||||
proxy_pass http://mjpgstreamer2/;
|
||||
}
|
||||
|
||||
location /webcam3/ {
|
||||
postpone_output 0;
|
||||
proxy_buffering off;
|
||||
proxy_ignore_headers X-Accel-Buffering;
|
||||
access_log off;
|
||||
error_log off;
|
||||
proxy_pass http://mjpgstreamer3/;
|
||||
}
|
||||
|
||||
location /webcam4/ {
|
||||
postpone_output 0;
|
||||
proxy_buffering off;
|
||||
proxy_ignore_headers X-Accel-Buffering;
|
||||
access_log off;
|
||||
error_log off;
|
||||
proxy_pass http://mjpgstreamer4/;
|
||||
}
|
||||
}
|
||||
25
kiauh/utils/res/upstreams.conf
Normal file
25
kiauh/utils/res/upstreams.conf
Normal file
@@ -0,0 +1,25 @@
|
||||
# /etc/nginx/conf.d/upstreams.conf
|
||||
upstream apiserver {
|
||||
ip_hash;
|
||||
server 127.0.0.1:7125;
|
||||
}
|
||||
|
||||
upstream mjpgstreamer1 {
|
||||
ip_hash;
|
||||
server 127.0.0.1:8080;
|
||||
}
|
||||
|
||||
upstream mjpgstreamer2 {
|
||||
ip_hash;
|
||||
server 127.0.0.1:8081;
|
||||
}
|
||||
|
||||
upstream mjpgstreamer3 {
|
||||
ip_hash;
|
||||
server 127.0.0.1:8082;
|
||||
}
|
||||
|
||||
upstream mjpgstreamer4 {
|
||||
ip_hash;
|
||||
server 127.0.0.1:8083;
|
||||
}
|
||||
328
kiauh/utils/system_utils.py
Normal file
328
kiauh/utils/system_utils.py
Normal file
@@ -0,0 +1,328 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# ======================================================================= #
|
||||
# Copyright (C) 2020 - 2023 Dominik Willner <th33xitus@gmail.com> #
|
||||
# #
|
||||
# This file is part of KIAUH - Klipper Installation And Update Helper #
|
||||
# https://github.com/dw-0/kiauh #
|
||||
# #
|
||||
# This file may be distributed under the terms of the GNU GPLv3 license #
|
||||
# ======================================================================= #
|
||||
|
||||
import os
|
||||
import shutil
|
||||
import socket
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
from pathlib import Path
|
||||
from typing import List, Literal
|
||||
|
||||
from kiauh.utils.input_utils import get_confirm
|
||||
from kiauh.utils.logger import Logger
|
||||
|
||||
|
||||
def kill(opt_err_msg: str = "") -> None:
|
||||
"""
|
||||
Kills the application |
|
||||
:param opt_err_msg: an optional, additional error message
|
||||
:return: None
|
||||
"""
|
||||
|
||||
if opt_err_msg:
|
||||
Logger.print_error(opt_err_msg)
|
||||
Logger.print_error("A critical error has occured. KIAUH was terminated.")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def parse_packages_from_file(source_file: Path) -> List[str]:
|
||||
"""
|
||||
Read the package names from bash scripts, when defined like:
|
||||
PKGLIST="package1 package2 package3" |
|
||||
:param source_file: path of the sourcefile to read from
|
||||
:return: A list of package names
|
||||
"""
|
||||
|
||||
packages = []
|
||||
print("Reading dependencies...")
|
||||
with open(source_file, "r") as file:
|
||||
for line in file:
|
||||
line = line.strip()
|
||||
if line.startswith("PKGLIST="):
|
||||
line = line.replace('"', "")
|
||||
line = line.replace("PKGLIST=", "")
|
||||
line = line.replace("${PKGLIST}", "")
|
||||
packages.extend(line.split())
|
||||
|
||||
return packages
|
||||
|
||||
|
||||
def create_python_venv(target: Path) -> None:
|
||||
"""
|
||||
Create a python 3 virtualenv at the provided target destination |
|
||||
:param target: Path where to create the virtualenv at
|
||||
:return: None
|
||||
"""
|
||||
Logger.print_status("Set up Python virtual environment ...")
|
||||
if not target.exists():
|
||||
try:
|
||||
command = ["python3", "-m", "venv", f"{target}"]
|
||||
result = subprocess.run(command, stderr=subprocess.PIPE, text=True)
|
||||
if result.returncode != 0 or result.stderr:
|
||||
Logger.print_error(f"{result.stderr}", prefix=False)
|
||||
Logger.print_error("Setup of virtualenv failed!")
|
||||
return
|
||||
|
||||
Logger.print_ok("Setup of virtualenv successfull!")
|
||||
except subprocess.CalledProcessError as e:
|
||||
Logger.print_error(f"Error setting up virtualenv:\n{e.output.decode()}")
|
||||
else:
|
||||
if get_confirm("Virtualenv already exists. Re-create?", default_choice=False):
|
||||
try:
|
||||
shutil.rmtree(target)
|
||||
create_python_venv(target)
|
||||
except OSError as e:
|
||||
log = f"Error removing existing virtualenv: {e.strerror}"
|
||||
Logger.print_error(log, False)
|
||||
else:
|
||||
Logger.print_info("Skipping re-creation of virtualenv ...")
|
||||
|
||||
|
||||
def update_python_pip(target: Path) -> None:
|
||||
"""
|
||||
Updates pip in the provided target destination |
|
||||
:param target: Path of the virtualenv
|
||||
:return: None
|
||||
"""
|
||||
Logger.print_status("Updating pip ...")
|
||||
try:
|
||||
command = [f"{target}/bin/pip", "install", "-U", "pip"]
|
||||
result = subprocess.run(command, stderr=subprocess.PIPE, text=True)
|
||||
if result.returncode != 0 or result.stderr:
|
||||
Logger.print_error(f"{result.stderr}", False)
|
||||
Logger.print_error("Updating pip failed!")
|
||||
return
|
||||
|
||||
Logger.print_ok("Updating pip successfull!")
|
||||
except subprocess.CalledProcessError as e:
|
||||
Logger.print_error(f"Error updating pip:\n{e.output.decode()}")
|
||||
|
||||
|
||||
def install_python_requirements(target: Path, requirements: Path) -> None:
|
||||
"""
|
||||
Installs the python packages based on a provided requirements.txt |
|
||||
:param target: Path of the virtualenv
|
||||
:param requirements: Path to the requirements.txt file
|
||||
:return: None
|
||||
"""
|
||||
update_python_pip(target)
|
||||
Logger.print_status("Installing Python requirements ...")
|
||||
try:
|
||||
command = [f"{target}/bin/pip", "install", "-r", f"{requirements}"]
|
||||
result = subprocess.run(command, stderr=subprocess.PIPE, text=True)
|
||||
if result.returncode != 0 or result.stderr:
|
||||
Logger.print_error(f"{result.stderr}", False)
|
||||
Logger.print_error("Installing Python requirements failed!")
|
||||
return
|
||||
|
||||
Logger.print_ok("Installing Python requirements successfull!")
|
||||
except subprocess.CalledProcessError as e:
|
||||
log = f"Error installing Python requirements:\n{e.output.decode()}"
|
||||
Logger.print_error(log)
|
||||
|
||||
|
||||
def update_system_package_lists(silent: bool, rls_info_change=False) -> None:
|
||||
"""
|
||||
Updates the systems package list |
|
||||
:param silent: Log info to the console or not
|
||||
:param rls_info_change: Flag for "--allow-releaseinfo-change"
|
||||
:return: None
|
||||
"""
|
||||
cache_mtime = 0
|
||||
cache_files = ["/var/lib/apt/periodic/update-success-stamp", "/var/lib/apt/lists"]
|
||||
for cache_file in cache_files:
|
||||
if Path(cache_file).exists():
|
||||
cache_mtime = max(cache_mtime, os.path.getmtime(cache_file))
|
||||
|
||||
update_age = int(time.time() - cache_mtime)
|
||||
update_interval = 6 * 3600 # 48hrs
|
||||
|
||||
if update_age <= update_interval:
|
||||
return
|
||||
|
||||
if not silent:
|
||||
Logger.print_status("Updating package list...")
|
||||
|
||||
try:
|
||||
command = ["sudo", "apt-get", "update"]
|
||||
if rls_info_change:
|
||||
command.append("--allow-releaseinfo-change")
|
||||
|
||||
result = subprocess.run(command, stderr=subprocess.PIPE, text=True)
|
||||
if result.returncode != 0 or result.stderr:
|
||||
Logger.print_error(f"{result.stderr}", False)
|
||||
Logger.print_error("Updating system package list failed!")
|
||||
return
|
||||
|
||||
Logger.print_ok("System package list updated successfully!")
|
||||
except subprocess.CalledProcessError as e:
|
||||
kill(f"Error updating system package list:\n{e.stderr.decode()}")
|
||||
|
||||
|
||||
def check_package_install(packages: List[str]) -> List[str]:
|
||||
"""
|
||||
Checks the system for installed packages |
|
||||
:param packages: List of strings of package names
|
||||
:return: A list containing the names of packages that are not installed
|
||||
"""
|
||||
not_installed = []
|
||||
for package in packages:
|
||||
command = ["dpkg-query", "-f'${Status}'", "--show", package]
|
||||
result = subprocess.run(
|
||||
command, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, text=True
|
||||
)
|
||||
if "installed" not in result.stdout.strip("'").split():
|
||||
not_installed.append(package)
|
||||
else:
|
||||
Logger.print_ok(f"{package} already installed.")
|
||||
|
||||
return not_installed
|
||||
|
||||
|
||||
def install_system_packages(packages: List[str]) -> None:
|
||||
"""
|
||||
Installs a list of system packages |
|
||||
:param packages: List of system package names
|
||||
:return: None
|
||||
"""
|
||||
try:
|
||||
command = ["sudo", "apt-get", "install", "-y"]
|
||||
for pkg in packages:
|
||||
command.append(pkg)
|
||||
subprocess.run(command, stderr=subprocess.PIPE, check=True)
|
||||
|
||||
Logger.print_ok("Packages installed successfully.")
|
||||
except subprocess.CalledProcessError as e:
|
||||
kill(f"Error installing packages:\n{e.stderr.decode()}")
|
||||
|
||||
|
||||
def mask_system_service(service_name: str) -> None:
|
||||
"""
|
||||
Mask a system service to prevent it from starting |
|
||||
:param service_name: name of the service to mask
|
||||
:return: None
|
||||
"""
|
||||
try:
|
||||
command = ["sudo", "systemctl", "mask", service_name]
|
||||
subprocess.run(command, stderr=subprocess.PIPE, check=True)
|
||||
except subprocess.CalledProcessError as e:
|
||||
log = f"Unable to mask system service {service_name}: {e.stderr.decode()}"
|
||||
Logger.print_error(log)
|
||||
raise
|
||||
|
||||
|
||||
# this feels hacky and not quite right, but for now it works
|
||||
# see: https://stackoverflow.com/questions/166506/finding-local-ip-addresses-using-pythons-stdlib
|
||||
def get_ipv4_addr() -> str:
|
||||
"""
|
||||
Helper function that returns the IPv4 of the current machine
|
||||
by opening a socket and sending a package to an arbitrary IP. |
|
||||
:return: Local IPv4 of the current machine
|
||||
"""
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
s.settimeout(0)
|
||||
try:
|
||||
# doesn't even have to be reachable
|
||||
s.connect(("192.255.255.255", 1))
|
||||
return s.getsockname()[0]
|
||||
except Exception:
|
||||
return "127.0.0.1"
|
||||
finally:
|
||||
s.close()
|
||||
|
||||
|
||||
def download_file(
|
||||
url: str, target_folder: str, target_name: str, show_progress=True
|
||||
) -> None:
|
||||
"""
|
||||
Helper method for downloading files from a provided URL |
|
||||
:param url: the url to the file
|
||||
:param target_folder: the target folder to download the file into
|
||||
:param target_name: the name of the downloaded file
|
||||
:param show_progress: show download progress or not
|
||||
:return: None
|
||||
"""
|
||||
target_path = os.path.join(target_folder, target_name)
|
||||
try:
|
||||
if show_progress:
|
||||
urllib.request.urlretrieve(url, target_path, download_progress)
|
||||
sys.stdout.write("\n")
|
||||
else:
|
||||
urllib.request.urlretrieve(url, target_path)
|
||||
except urllib.error.HTTPError as e:
|
||||
Logger.print_error(f"Download failed! HTTP error occured: {e}")
|
||||
raise
|
||||
except urllib.error.URLError as e:
|
||||
Logger.print_error(f"Download failed! URL error occured: {e}")
|
||||
raise
|
||||
except Exception as e:
|
||||
Logger.print_error(f"Download failed! An error occured: {e}")
|
||||
raise
|
||||
|
||||
|
||||
def download_progress(block_num, block_size, total_size) -> None:
|
||||
"""
|
||||
Reporthook method for urllib.request.urlretrieve() method call in download_file() |
|
||||
:param block_num:
|
||||
:param block_size:
|
||||
:param total_size: total filesize in bytes
|
||||
:return: None
|
||||
"""
|
||||
downloaded = block_num * block_size
|
||||
percent = 100 if downloaded >= total_size else downloaded / total_size * 100
|
||||
mb = 1024 * 1024
|
||||
progress = int(percent / 5)
|
||||
remaining = "-" * (20 - progress)
|
||||
dl = f"\rDownloading: [{'#' * progress}{remaining}]{percent:.2f}% ({downloaded/mb:.2f}/{total_size/mb:.2f}MB)"
|
||||
sys.stdout.write(dl)
|
||||
sys.stdout.flush()
|
||||
|
||||
|
||||
def set_nginx_permissions() -> None:
|
||||
"""
|
||||
Check if permissions of the users home directory
|
||||
grant execution rights to group and other and set them if not set.
|
||||
Required permissions for NGINX to be able to serve Mainsail/Fluidd.
|
||||
This seems to have become necessary with Ubuntu 21+. |
|
||||
:return: None
|
||||
"""
|
||||
cmd1 = f"ls -ld {Path.home()} | cut -d' ' -f1"
|
||||
homedir_perm = subprocess.run(cmd1, shell=True, stdout=subprocess.PIPE, text=True)
|
||||
homedir_perm = homedir_perm.stdout
|
||||
|
||||
if homedir_perm.count("x") < 3:
|
||||
Logger.print_status("Granting NGINX the required permissions ...")
|
||||
subprocess.run(["chmod", "og+x", Path.home()])
|
||||
Logger.print_ok("Permissions granted.")
|
||||
|
||||
|
||||
def control_systemd_service(
|
||||
name: str, action: Literal["start", "stop", "restart", "disable"]
|
||||
) -> None:
|
||||
"""
|
||||
Helper method to execute several actions for a specific systemd service. |
|
||||
:param name: the service name
|
||||
:param action: Either "start", "stop", "restart" or "disable"
|
||||
:return: None
|
||||
"""
|
||||
try:
|
||||
Logger.print_status(f"{action.capitalize()} {name}.service ...")
|
||||
command = ["sudo", "systemctl", action, f"{name}.service"]
|
||||
subprocess.run(command, stderr=subprocess.PIPE, check=True)
|
||||
Logger.print_ok(f"OK!")
|
||||
except subprocess.CalledProcessError as e:
|
||||
log = f"Failed to {action} {name}.service: {e.stderr.decode()}"
|
||||
Logger.print_error(log)
|
||||
raise
|
||||
13
pyproject.toml
Normal file
13
pyproject.toml
Normal file
@@ -0,0 +1,13 @@
|
||||
[tool.black]
|
||||
line-length = 88
|
||||
target-version = ['py38']
|
||||
include = '\.pyi?$'
|
||||
exclude = '''
|
||||
(
|
||||
\.git/
|
||||
| \.github/
|
||||
| docs/
|
||||
| resources/
|
||||
| scripts/
|
||||
)
|
||||
'''
|
||||
@@ -1,10 +1,10 @@
|
||||
#!/bin/bash
|
||||
#!/usr/bin/env bash
|
||||
|
||||
#####################################################################
|
||||
### Please set the paths accordingly. In case you don't have all ###
|
||||
### the listed folders, just keep that line commented out. ###
|
||||
#####################################################################
|
||||
### Path to your config folder you want to backup
|
||||
### Path to your config folder you want to back up
|
||||
#config_folder=~/klipper_config
|
||||
|
||||
### Path to your Klipper folder, by default that is '~/klipper'
|
||||
@@ -26,35 +26,38 @@
|
||||
#####################################################################
|
||||
################ !!! DO NOT EDIT BELOW THIS LINE !!! ################
|
||||
#####################################################################
|
||||
grab_version(){
|
||||
if [ ! -z "$klipper_folder" ]; then
|
||||
cd "$klipper_folder"
|
||||
grab_version() {
|
||||
local klipper_commit moonraker_commit
|
||||
local mainsail_ver fluidd_ver
|
||||
|
||||
if [[ -n ${klipper_folder} ]]; then
|
||||
cd "${klipper_folder}"
|
||||
klipper_commit=$(git rev-parse --short=7 HEAD)
|
||||
m1="Klipper on commit: $klipper_commit"
|
||||
cd ..
|
||||
m1="Klipper on commit: ${klipper_commit}"
|
||||
fi
|
||||
if [ ! -z "$moonraker_folder" ]; then
|
||||
cd "$moonraker_folder"
|
||||
if [[ -n ${moonraker_folder} ]]; then
|
||||
cd "${moonraker_folder}"
|
||||
moonraker_commit=$(git rev-parse --short=7 HEAD)
|
||||
m2="Moonraker on commit: $moonraker_commit"
|
||||
cd ..
|
||||
m2="Moonraker on commit: ${moonraker_commit}"
|
||||
fi
|
||||
if [ ! -z "$mainsail_folder" ]; then
|
||||
mainsail_ver=$(head -n 1 $mainsail_folder/.version)
|
||||
m3="Mainsail version: $mainsail_ver"
|
||||
if [[ -n ${mainsail_folder} ]]; then
|
||||
mainsail_ver=$(head -n 1 "${mainsail_folder}/.version")
|
||||
m3="Mainsail version: ${mainsail_ver}"
|
||||
fi
|
||||
if [ ! -z "$fluidd_folder" ]; then
|
||||
fluidd_ver=$(head -n 1 $fluidd_folder/.version)
|
||||
m4="Fluidd version: $fluidd_ver"
|
||||
if [[ -n ${fluidd_folder} ]]; then
|
||||
fluidd_ver=$(head -n 1 "${fluidd_folder}/.version")
|
||||
m4="Fluidd version: ${fluidd_ver}"
|
||||
fi
|
||||
}
|
||||
|
||||
push_config(){
|
||||
cd $config_folder
|
||||
push_config() {
|
||||
local current_date
|
||||
|
||||
cd "${config_folder}" || exit 1
|
||||
git pull
|
||||
git add .
|
||||
current_date=$(date +"%Y-%m-%d %T")
|
||||
git commit -m "Autocommit from $current_date" -m "$m1" -m "$m2" -m "$m3" -m "$m4"
|
||||
git commit -m "Autocommit from ${current_date}" -m "${m1}" -m "${m2}" -m "${m3}" -m "${m4}"
|
||||
git push
|
||||
}
|
||||
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
map $http_upgrade $connection_upgrade {
|
||||
default upgrade;
|
||||
'' close;
|
||||
}
|
||||
|
||||
upstream dwc2 {
|
||||
server 127.0.0.1:4750;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
#listen [::]:80;
|
||||
|
||||
location / {
|
||||
proxy_pass http://dwc2/;
|
||||
proxy_set_header Host $http_host;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
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;
|
||||
#proxy_set_header X-Script-Name /;
|
||||
proxy_http_version 1.1;
|
||||
client_max_body_size 0;
|
||||
}
|
||||
}
|
||||
7
resources/example.printer.cfg
Normal file
7
resources/example.printer.cfg
Normal file
@@ -0,0 +1,7 @@
|
||||
[mcu]
|
||||
serial: /dev/serial/by-id/<your-mcu-id>
|
||||
|
||||
[printer]
|
||||
kinematics: none
|
||||
max_velocity: 1000
|
||||
max_accel: 1000
|
||||
@@ -1,10 +1,10 @@
|
||||
# /etc/nginx/sites-available/<<UI>>
|
||||
# /etc/nginx/sites-available/fluidd
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
|
||||
access_log /var/log/nginx/<<UI>>-access.log;
|
||||
error_log /var/log/nginx/<<UI>>-error.log;
|
||||
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;
|
||||
@@ -16,8 +16,8 @@ server {
|
||||
gzip_http_version 1.1;
|
||||
gzip_types text/plain text/css text/xml text/javascript application/javascript application/x-javascript application/json application/xml;
|
||||
|
||||
# web_path from <<UI>> static files
|
||||
root /home/pi/<<UI>>;
|
||||
# web_path from fluidd static files
|
||||
root /home/pi/fluidd;
|
||||
|
||||
index index.html;
|
||||
server_name _;
|
||||
@@ -55,6 +55,7 @@ server {
|
||||
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;
|
||||
proxy_read_timeout 600;
|
||||
}
|
||||
|
||||
location /webcam/ {
|
||||
@@ -1,59 +0,0 @@
|
||||
################################################################################
|
||||
# ~~~~~~~~~~~~~~~~~~~~~~~~ AUTOCREATED WITH KIAUH ~~~~~~~~~~~~~~~~~~~~~~~~~~ #
|
||||
################################################################################
|
||||
# Recommended macros and config entries if you use Mainsail or Fluidd! #
|
||||
# You can edit or delete those macros if you already defined them elsewhere! #
|
||||
################################################################################
|
||||
|
||||
[pause_resume]
|
||||
|
||||
[display_status]
|
||||
|
||||
[gcode_macro CANCEL_PRINT]
|
||||
rename_existing: BASE_CANCEL_PRINT
|
||||
gcode:
|
||||
TURN_OFF_HEATERS
|
||||
CLEAR_PAUSE
|
||||
SDCARD_RESET_FILE
|
||||
BASE_CANCEL_PRINT
|
||||
|
||||
[gcode_macro PAUSE]
|
||||
rename_existing: BASE_PAUSE
|
||||
gcode:
|
||||
##### set defaults #####
|
||||
{% set x = params.X|default(230) %} #edit to your park position
|
||||
{% set y = params.Y|default(230) %} #edit to your park position
|
||||
{% set z = params.Z|default(10)|float %} #edit to your park position
|
||||
{% set e = params.E|default(1) %} #edit to your retract length
|
||||
##### calculate save lift position #####
|
||||
{% set max_z = printer.toolhead.axis_maximum.z|float %}
|
||||
{% set act_z = printer.toolhead.position.z|float %}
|
||||
{% set lift_z = z|abs %}
|
||||
{% if act_z < (max_z - lift_z) %}
|
||||
{% set z_safe = lift_z %}
|
||||
{% else %}
|
||||
{% set z_safe = max_z - act_z %}
|
||||
{% endif %}
|
||||
##### end of definitions #####
|
||||
SAVE_GCODE_STATE NAME=PAUSE_state
|
||||
BASE_PAUSE
|
||||
G91
|
||||
G1 E-{e} F2100
|
||||
G1 Z{z_safe}
|
||||
G90
|
||||
G1 X{x} Y{y} F6000
|
||||
|
||||
|
||||
[gcode_macro RESUME]
|
||||
rename_existing: BASE_RESUME
|
||||
gcode:
|
||||
##### set defaults #####
|
||||
{% set e = params.E|default(1) %} #edit to your retract length
|
||||
G91
|
||||
G1 E{e} F2100
|
||||
G90
|
||||
RESTORE_GCODE_STATE NAME=PAUSE_state MOVE=1
|
||||
BASE_RESUME
|
||||
|
||||
################################################################################
|
||||
################################################################################
|
||||
1
resources/klipper.env
Normal file
1
resources/klipper.env
Normal file
@@ -0,0 +1 @@
|
||||
KLIPPER_ARGS="%KLIPPER_DIR%/klippy/klippy.py %CFG% -I %PRINTER% -l %LOG% -a %UDS%"
|
||||
@@ -1,7 +1,8 @@
|
||||
#Systemd service file for klipper
|
||||
[Unit]
|
||||
Description=Starts klipper instance %INST% on startup
|
||||
After=network.target
|
||||
Description=Klipper 3D Printer Firmware SV1
|
||||
Documentation=https://www.klipper3d.org/
|
||||
After=network-online.target
|
||||
Wants=udev.target
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
@@ -10,6 +11,8 @@ WantedBy=multi-user.target
|
||||
Type=simple
|
||||
User=%USER%
|
||||
RemainAfterExit=yes
|
||||
ExecStart=%KL_ENV%/bin/python %KL_DIR%/klippy/klippy.py %P_CFG% -I %P_TMP% -l %KL_LOG% -a %KL_UDS%
|
||||
WorkingDirectory=%KLIPPER_DIR%
|
||||
EnvironmentFile=%ENV_FILE%
|
||||
ExecStart=%ENV%/bin/python $KLIPPER_ARGS
|
||||
Restart=always
|
||||
RestartSec=10
|
||||
96
resources/mainsail
Normal file
96
resources/mainsail
Normal file
@@ -0,0 +1,96 @@
|
||||
# /etc/nginx/sites-available/mainsail
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
|
||||
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/javascript application/x-javascript application/json application/xml;
|
||||
|
||||
# web_path from mainsail static files
|
||||
root /home/pi/mainsail;
|
||||
|
||||
index index.html;
|
||||
server_name _;
|
||||
|
||||
# disable max upload size checks
|
||||
client_max_body_size 0;
|
||||
|
||||
# disable proxy request buffering
|
||||
proxy_request_buffering off;
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
|
||||
location = /index.html {
|
||||
add_header Cache-Control "no-store, no-cache, must-revalidate";
|
||||
}
|
||||
|
||||
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 ~ ^/(printer|api|access|machine|server)/ {
|
||||
proxy_pass http://apiserver$request_uri;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_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_set_header X-Scheme $scheme;
|
||||
proxy_read_timeout 600;
|
||||
}
|
||||
|
||||
location /webcam/ {
|
||||
postpone_output 0;
|
||||
proxy_buffering off;
|
||||
proxy_ignore_headers X-Accel-Buffering;
|
||||
access_log off;
|
||||
error_log off;
|
||||
proxy_pass http://mjpgstreamer1/;
|
||||
}
|
||||
|
||||
location /webcam2/ {
|
||||
postpone_output 0;
|
||||
proxy_buffering off;
|
||||
proxy_ignore_headers X-Accel-Buffering;
|
||||
access_log off;
|
||||
error_log off;
|
||||
proxy_pass http://mjpgstreamer2/;
|
||||
}
|
||||
|
||||
location /webcam3/ {
|
||||
postpone_output 0;
|
||||
proxy_buffering off;
|
||||
proxy_ignore_headers X-Accel-Buffering;
|
||||
access_log off;
|
||||
error_log off;
|
||||
proxy_pass http://mjpgstreamer3/;
|
||||
}
|
||||
|
||||
location /webcam4/ {
|
||||
postpone_output 0;
|
||||
proxy_buffering off;
|
||||
proxy_ignore_headers X-Accel-Buffering;
|
||||
access_log off;
|
||||
error_log off;
|
||||
proxy_pass http://mjpgstreamer4/;
|
||||
}
|
||||
}
|
||||
79
resources/mjpg-streamer/webcam.txt
Normal file
79
resources/mjpg-streamer/webcam.txt
Normal file
@@ -0,0 +1,79 @@
|
||||
### Windows users: To edit this file use Notepad++, VSCode, Atom or SublimeText.
|
||||
### Do not use Notepad or WordPad.
|
||||
|
||||
### MacOSX users: If you use Textedit to edit this file make sure to use
|
||||
### "plain text format" and "disable smart quotes" in "Textedit > Preferences"
|
||||
|
||||
### Configure which camera to use
|
||||
#
|
||||
# Available options are:
|
||||
# - auto: tries first usb webcam, if that's not available tries raspi cam
|
||||
# - usb: only tries usb webcam
|
||||
# - raspi: only tries raspi cam
|
||||
#
|
||||
# Defaults to auto
|
||||
#
|
||||
#camera="auto"
|
||||
|
||||
### Additional options to supply to MJPG Streamer for the USB camera
|
||||
#
|
||||
# See https://faq.octoprint.org/mjpg-streamer-config for available options
|
||||
#
|
||||
# Defaults to a resolution of 640x480 px and a framerate of 10 fps
|
||||
#
|
||||
#camera_usb_options="-r 640x480 -f 10"
|
||||
|
||||
### Additional webcam devices known to cause problems with -f
|
||||
#
|
||||
# Apparently there a some devices out there that with the current
|
||||
# mjpg_streamer release do not support the -f parameter (for specifying
|
||||
# the capturing framerate) and will just refuse to output an image if it
|
||||
# is supplied.
|
||||
#
|
||||
# The webcam daemon will detect those devices by their USB Vendor and Product
|
||||
# ID and remove the -f parameter from the options provided to mjpg_streamer.
|
||||
#
|
||||
# By default, this is done for the following devices:
|
||||
# Logitech C170 (046d:082b)
|
||||
# GEMBIRD (1908:2310)
|
||||
# Genius F100 (0458:708c)
|
||||
# Cubeternet GL-UPC822 UVC WebCam (1e4e:0102)
|
||||
#
|
||||
# Using the following option it is possible to add additional devices. If
|
||||
# your webcam happens to show above symptoms, try determining your cam's
|
||||
# vendor and product id via lsusb, activating the line below by removing # and
|
||||
# adding it, e.g. for two broken cameras "aabb:ccdd" and "aabb:eeff"
|
||||
#
|
||||
# additional_brokenfps_usb_devices=("aabb:ccdd" "aabb:eeff")
|
||||
#
|
||||
#
|
||||
#additional_brokenfps_usb_devices=()
|
||||
|
||||
### Additional options to supply to MJPG Streamer for the RasPi Cam
|
||||
#
|
||||
# See https://faq.octoprint.org/mjpg-streamer-config for available options
|
||||
#
|
||||
# Defaults to 10fps
|
||||
#
|
||||
#camera_raspi_options="-fps 10"
|
||||
|
||||
### Configuration of camera HTTP output
|
||||
#
|
||||
# Usually you should NOT need to change this at all! Only touch if you
|
||||
# know what you are doing and what the parameters mean.
|
||||
#
|
||||
# Below settings are used in the mjpg-streamer call like this:
|
||||
#
|
||||
# -o "output_http.so -w $camera_http_webroot $camera_http_options"
|
||||
#
|
||||
# Current working directory is the mjpg-streamer base directory.
|
||||
#
|
||||
#camera_http_webroot="./www-mainsail"
|
||||
#camera_http_options="-n"
|
||||
|
||||
### EXPERIMENTAL
|
||||
# Support for different streamer types.
|
||||
#
|
||||
# Available options:
|
||||
# mjpeg [default] - stable MJPG-streamer
|
||||
#camera_streamer=mjpeg
|
||||
303
resources/mjpg-streamer/webcamd
Normal file
303
resources/mjpg-streamer/webcamd
Normal file
@@ -0,0 +1,303 @@
|
||||
#!/bin/bash
|
||||
|
||||
########################################################################
|
||||
### DO NOT EDIT THIS FILE TO CHANGE THE CONFIG!!! ###
|
||||
### ---------------------------------------------------------------- ###
|
||||
### There is no need to edit this file for changing resolution, ###
|
||||
### frame rates or any other mjpg-streamer parameters. Please edit ###
|
||||
### /home/pi/klipper_config/webcam.txt instead - that's what it's ###
|
||||
### there for! You can even do this with your Pi powered down by ###
|
||||
### directly accessing the file when using the SD card as thumb ###
|
||||
### drive in your regular computer. ###
|
||||
########################################################################
|
||||
|
||||
MJPGSTREAMER_HOME=/home/pi/mjpg-streamer
|
||||
MJPGSTREAMER_INPUT_USB="input_uvc.so"
|
||||
MJPGSTREAMER_INPUT_RASPICAM="input_raspicam.so"
|
||||
|
||||
brokenfps_usb_devices=("046d:082b" "1908:2310" "0458:708c" "1e4e:0102" "0471:0311" "038f:6001" "046d:0804" "046d:0825" "046d:0994" "0ac8:3450")
|
||||
|
||||
config_dir="/home/pi/klipper_config"
|
||||
|
||||
echo "Starting up webcamDaemon..."
|
||||
echo ""
|
||||
|
||||
cfg_files=()
|
||||
#cfg_files+=/boot/mainsail.txt
|
||||
if [[ -d ${config_dir} ]]; then
|
||||
cfg_files+=( `ls ${config_dir}/webcam*.txt` )
|
||||
fi
|
||||
|
||||
array_camera_config=()
|
||||
array_camera=()
|
||||
array_camera_usb_options=()
|
||||
array_camera_usb_device=()
|
||||
array_camera_raspi_options=()
|
||||
array_camera_http_webroot=()
|
||||
array_camera_http_options=()
|
||||
array_additional_brokenfps_usb_devices=()
|
||||
array_camera_device=()
|
||||
array_assigned_device=()
|
||||
|
||||
echo "--- Configuration: ----------------------------"
|
||||
for cfg_file in ${cfg_files[@]}; do
|
||||
# init configuration - DO NOT EDIT, USE /home/pi/klipper_config/webcam*.txt INSTEAD!
|
||||
camera="auto"
|
||||
camera_usb_options="-r 640x480 -f 10"
|
||||
camera_raspi_options="-fps 10"
|
||||
camera_http_webroot="./www-mjpgstreamer"
|
||||
camera_http_options="-n"
|
||||
additional_brokenfps_usb_devices=()
|
||||
|
||||
if [[ -e ${cfg_file} ]]; then
|
||||
source "$cfg_file"
|
||||
fi
|
||||
usb_options="$camera_usb_options"
|
||||
|
||||
# if webcam device is explicitly given in /home/pi/klipper_config/webcam*.txt, save the path of the device
|
||||
# to a variable and remove its parameter from usb_options
|
||||
extracted_device=`echo $usb_options | sed 's@.*-d \(/dev/\(video[0-9]\+\|v4l/[^ ]*\)\).*@\1@'`
|
||||
if [ "$extracted_device" != "$usb_options" ]
|
||||
then
|
||||
# the camera options refer to a device, save it in a variable
|
||||
# replace video device parameter with empty string and strip extra whitespace
|
||||
usb_options=`echo $usb_options | sed 's/\-d \/dev\/\(video[0-9]\+\|v4l\/[^ ]*\)//g' | awk '$1=$1'`
|
||||
else
|
||||
extracted_device=""
|
||||
fi
|
||||
|
||||
# echo configuration
|
||||
echo "cfg_file: $cfg_file"
|
||||
echo "camera: $camera"
|
||||
echo "usb options: $camera_usb_options"
|
||||
echo "raspi options: $camera_raspi_options"
|
||||
echo "http options: -w $camera_http_webroot $camera_http_options"
|
||||
echo ""
|
||||
echo "Explicitly USB device: $extracted_device"
|
||||
echo "-----------------------------------------------"
|
||||
echo ""
|
||||
|
||||
array_camera_config+=( $cfg_file )
|
||||
array_camera+=( $camera )
|
||||
array_camera_usb_options+=("$usb_options")
|
||||
array_camera_usb_device+=("$extracted_device")
|
||||
array_camera_raspi_options+=("$camera_raspi_options")
|
||||
array_camera_http_webroot+=("$camera_http_webroot")
|
||||
array_camera_http_options+=("$camera_http_options")
|
||||
array_camera_brokenfps_usb_devices+=("${brokenfps_usb_devices[*]} ${additional_brokenfps_usb_devices[*]}")
|
||||
array_camera_device+=("")
|
||||
done
|
||||
|
||||
# check if array contains a string
|
||||
function containsString() {
|
||||
local e match="$1"
|
||||
shift
|
||||
for e; do [[ "$e" == "$match" ]] && return 0; done
|
||||
return 1
|
||||
}
|
||||
|
||||
# cleans up when the script receives a SIGINT or SIGTERM
|
||||
function cleanup() {
|
||||
# make sure that all child processed die when we die
|
||||
local pids=$(jobs -pr)
|
||||
[ -n "$pids" ] && kill $pids
|
||||
exit 0
|
||||
}
|
||||
|
||||
# says goodbye when the script shuts down
|
||||
function goodbye() {
|
||||
# say goodbye
|
||||
echo ""
|
||||
echo "Goodbye..."
|
||||
echo ""
|
||||
}
|
||||
|
||||
# runs MJPG Streamer, using the provided input plugin + configuration
|
||||
function runMjpgStreamer {
|
||||
input=$1
|
||||
|
||||
# There are problems with 0x000137ab firmware on VL805 (Raspberry Pi 4}).
|
||||
# Try to autodetect offending firmware and temporarily fix the issue
|
||||
# by changing power management mode
|
||||
echo "Checking for VL805 (Raspberry Pi 4)..."
|
||||
if [[ -f /usr/bin/vl805 ]]; then
|
||||
VL805_VERSION=$(/usr/bin/vl805)
|
||||
VL805_VERSION=${VL805_VERSION#*: }
|
||||
echo " - version 0x${VL805_VERSION} detected"
|
||||
case "$VL805_VERSION" in
|
||||
00013701)
|
||||
echo " - nothing to be done. It shouldn't cause USB problems."
|
||||
;;
|
||||
000137ab)
|
||||
echo -e " - \e[31mThis version is known to cause problems with USB cameras.\e[39m"
|
||||
echo -e " You may want to downgrade to 0x0013701."
|
||||
echo -e " - [FIXING] Trying the setpci -s 01:00.0 0xD4.B=0x41 hack to mitigate the"
|
||||
echo -e " issue. It disables ASPM L1 on the VL805. Your board may (or may not) get"
|
||||
echo -e " slightly hotter. For details see:"
|
||||
echo -e " https://www.raspberrypi.org/forums/viewtopic.php?f=28&t=244421"
|
||||
setpci -s 01:00.0 0xD4.B=0x41
|
||||
;;
|
||||
*)
|
||||
echo " - unknown firmware version. Doing nothing."
|
||||
;;
|
||||
esac
|
||||
else
|
||||
echo " - It seems that you don't have VL805 (Raspberry Pi 4)."
|
||||
echo " There should be no problems with USB (a.k.a. select() timeout)"
|
||||
fi
|
||||
|
||||
pushd $MJPGSTREAMER_HOME > /dev/null 2>&1
|
||||
echo Running ./mjpg_streamer -o "output_http.so -w $camera_http_webroot $camera_http_options" -i "$input"
|
||||
LD_LIBRARY_PATH=. ./mjpg_streamer -o "output_http.so -w $camera_http_webroot $camera_http_options" -i "$input" &
|
||||
sleep 1 &
|
||||
sleep_pid=$!
|
||||
wait ${sleep_pid}
|
||||
popd > /dev/null 2>&1
|
||||
}
|
||||
|
||||
# starts up the RasPiCam
|
||||
function startRaspi {
|
||||
logger -s "Starting Raspberry Pi camera"
|
||||
runMjpgStreamer "$MJPGSTREAMER_INPUT_RASPICAM $camera_raspi_options"
|
||||
}
|
||||
|
||||
# starts up the USB webcam
|
||||
function startUsb {
|
||||
options="$usb_options"
|
||||
device="video0"
|
||||
|
||||
# check for parameter and set the device if it is given as a parameter
|
||||
input=$1
|
||||
if [[ -n $input ]]; then
|
||||
device=`basename "$input"`
|
||||
fi
|
||||
|
||||
# add video device into options
|
||||
options="$options -d /dev/$device"
|
||||
|
||||
uevent_file="/sys/class/video4linux/$device/device/uevent"
|
||||
if [ -e $uevent_file ]; then
|
||||
# let's see what kind of webcam we have here, fetch vid and pid...
|
||||
product=`cat $uevent_file | grep PRODUCT | cut -d"=" -f2`
|
||||
vid=`echo $product | cut -d"/" -f1`
|
||||
pid=`echo $product | cut -d"/" -f2`
|
||||
vidpid=`printf "%04x:%04x" "0x$vid" "0x$pid"`
|
||||
|
||||
# ... then look if it is in our list of known broken-fps-devices and if so remove
|
||||
# the -f parameter from the options (if it's in there, else that's just a no-op)
|
||||
for identifier in ${brokenfps_usb_devices[@]};
|
||||
do
|
||||
if [ "$vidpid" = "$identifier" ]; then
|
||||
echo
|
||||
echo "Camera model $vidpid is known to not work with -f parameter, stripping it out"
|
||||
echo
|
||||
options=`echo $options | sed -e "s/\(\s\+\|^\)-f\s\+[0-9]\+//g"`
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
logger -s "Starting USB webcam"
|
||||
runMjpgStreamer "$MJPGSTREAMER_INPUT_USB $options"
|
||||
}
|
||||
|
||||
# make sure our cleanup function gets called when we receive SIGINT, SIGTERM
|
||||
trap "cleanup" SIGINT SIGTERM
|
||||
# say goodbye when we EXIT
|
||||
trap "goodbye" EXIT
|
||||
|
||||
# we need this to prevent the later calls to vcgencmd from blocking
|
||||
# I have no idea why, but that's how it is...
|
||||
vcgencmd version > /dev/null 2>&1
|
||||
|
||||
# keep mjpg streamer running if some camera is attached
|
||||
while true; do
|
||||
|
||||
# get list of usb video devices into an array
|
||||
video_devices=($(find /dev -regextype sed -regex '\/dev/video[0-9]\+' | sort -nk1.11 2> /dev/null))
|
||||
|
||||
# add list of raspi camera into an array
|
||||
if [ "`vcgencmd get_camera`" = "supported=1 detected=1" ]; then
|
||||
video_devices+=( "raspi" )
|
||||
fi
|
||||
|
||||
echo "Found video devices:"
|
||||
printf '%s\n' "${video_devices[@]}"
|
||||
|
||||
for scan_mode in "usb" "usb-auto" "raspi" "auto"; do
|
||||
camera=$scan_mode
|
||||
if [[ "usb-auto" == "$scan_mode" ]]; then
|
||||
camera="usb"
|
||||
fi
|
||||
for ((i=0;i<${#array_camera[@]};i++)); do
|
||||
if [[ -z ${array_camera_device[${i}]} ]] && [[ $camera == ${array_camera[${i}]} ]]; then
|
||||
camera_config="${array_camera_config[${i}]}"
|
||||
usb_options="${array_camera_usb_options[${i}]}"
|
||||
camera_usb_device="${array_camera_usb_device[${i}]}"
|
||||
camera_raspi_options="${array_camera_raspi_options[${i}]}"
|
||||
camera_http_webroot="${array_camera_http_webroot[${i}]}"
|
||||
camera_http_options="${array_camera_http_options[${i}]}"
|
||||
brokenfps_usb_devices="${array_camera_brokenfps_usb_devices[${i}]}"
|
||||
if [[ ${camera_usb_device} ]] && { [[ "usb" == ${scan_mode} ]] || [[ "auto" == ${scan_mode} ]]; }; then
|
||||
# usb device is explicitly set in options
|
||||
usb_device_path=`readlink -f ${camera_usb_device}`
|
||||
if containsString "$usb_device_path" "${array_camera_device[@]}"; then
|
||||
if [[ "auto" != ${scan_mode} ]]; then
|
||||
array_camera_device[${i}]="alredy_in_use"
|
||||
echo "config file='$camera_config':Video device already in use."
|
||||
continue
|
||||
fi
|
||||
elif containsString "$usb_device_path" "${video_devices[@]}"; then
|
||||
array_camera_device[${i}]="$usb_device_path"
|
||||
# explicitly set usb device was found in video_devices array, start usb with the found device
|
||||
echo "config file='$camera_config':USB device was set in options and found in devices, start MJPG-streamer with the configured USB video device: $usb_device_path"
|
||||
startUsb "$usb_device_path"
|
||||
continue
|
||||
fi
|
||||
elif [[ -z ${camera_usb_device} ]] && { [[ "usb-auto" == ${scan_mode} ]] || [[ "auto" == ${scan_mode} ]]; }; then
|
||||
for video_device in "${video_devices[@]}"; do
|
||||
if [[ "raspi" != "$video_device" ]]; then
|
||||
if containsString "$video_device" "${array_camera_device[@]}"; then
|
||||
: #already in use
|
||||
else
|
||||
array_camera_device[${i}]="$video_device"
|
||||
# device is not set explicitly in options, start usb with first found usb camera as the device
|
||||
echo "config file='$camera_config':USB device was not set in options, start MJPG-streamer with the first found video device: ${video_device}"
|
||||
startUsb "${video_device}"
|
||||
break
|
||||
fi
|
||||
fi
|
||||
done
|
||||
if [[ -n ${array_camera_device[${i}]} ]]; then
|
||||
continue
|
||||
fi
|
||||
fi
|
||||
if [[ "raspi" == ${scan_mode} ]] || [[ "auto" == ${scan_mode} ]]; then
|
||||
video_device="raspi"
|
||||
if containsString "$video_device" "${array_camera_device[@]}"; then
|
||||
if [[ "auto" != ${scan_mode} ]]; then
|
||||
array_camera_device[${i}]="alredy_in_use"
|
||||
echo "config file='$camera_config':RasPiCam device already in use."
|
||||
fi
|
||||
elif containsString "$video_device" "${video_devices[@]}"; then
|
||||
array_camera_device[${i}]="$video_device"
|
||||
echo "config file='$camera_config':Start MJPG-streamer with video device: ${video_device}"
|
||||
startRaspi
|
||||
sleep 30 &
|
||||
sleep_pid=$!
|
||||
wait ${sleep_pid}
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
done
|
||||
done
|
||||
array_assigned_device=( ${array_camera_device[*]} )
|
||||
if [[ ${#array_camera[@]} -eq ${#array_assigned_device[@]} ]]; then
|
||||
echo "Done bring up all configured video device"
|
||||
exit 0
|
||||
else
|
||||
echo "Scan again in two minutes"
|
||||
sleep 120 &
|
||||
sleep_pid=$!
|
||||
wait ${sleep_pid}
|
||||
fi
|
||||
done
|
||||
1
resources/moonraker-telegram-bot.env
Normal file
1
resources/moonraker-telegram-bot.env
Normal file
@@ -0,0 +1 @@
|
||||
TELEGRAM_BOT_ARGS="%TELEGRAM_BOT_DIR%/bot/main.py -c %CFG% -l %LOG%"
|
||||
16
resources/moonraker-telegram-bot.service
Normal file
16
resources/moonraker-telegram-bot.service
Normal file
@@ -0,0 +1,16 @@
|
||||
[Unit]
|
||||
Description=Moonraker Telegram Bot SV1 %INST%
|
||||
Documentation=https://github.com/nlef/moonraker-telegram-bot/wiki
|
||||
After=network-online.target
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=%USER%
|
||||
WorkingDirectory=%TELEGRAM_BOT_DIR%
|
||||
EnvironmentFile=%ENV_FILE%
|
||||
ExecStart=%ENV%/bin/python $TELEGRAM_BOT_ARGS
|
||||
Restart=always
|
||||
RestartSec=10
|
||||
@@ -1,12 +1,8 @@
|
||||
[server]
|
||||
host: 0.0.0.0
|
||||
port: %PORT%
|
||||
enable_debug_logging: False
|
||||
klippy_uds_address: %UDS%
|
||||
|
||||
[database]
|
||||
database_path: %MR_DB%
|
||||
|
||||
[authorization]
|
||||
trusted_clients:
|
||||
%LAN%
|
||||
@@ -18,16 +14,12 @@ trusted_clients:
|
||||
FE80::/10
|
||||
::1/128
|
||||
cors_domains:
|
||||
http://*.lan
|
||||
http://*.local
|
||||
https://my.mainsail.xyz
|
||||
http://my.mainsail.xyz
|
||||
https://app.fluidd.xyz
|
||||
http://app.fluidd.xyz
|
||||
|
||||
[file_manager]
|
||||
config_path: %CFG%
|
||||
log_path: %LOG%
|
||||
*.lan
|
||||
*.local
|
||||
*://localhost
|
||||
*://localhost:*
|
||||
*://my.mainsail.xyz
|
||||
*://app.fluidd.xyz
|
||||
|
||||
[octoprint_compat]
|
||||
|
||||
@@ -36,21 +28,3 @@ log_path: %LOG%
|
||||
[update_manager]
|
||||
channel: dev
|
||||
refresh_interval: 168
|
||||
|
||||
[update_manager mainsail]
|
||||
type: web
|
||||
repo: mainsail-crew/mainsail
|
||||
path: ~/mainsail
|
||||
|
||||
[update_manager fluidd]
|
||||
type: web
|
||||
repo: fluidd-core/fluidd
|
||||
path: ~/fluidd
|
||||
|
||||
#[update_manager KlipperScreen]
|
||||
#type: git_repo
|
||||
#path: /home/%USER%/KlipperScreen
|
||||
#origin: https://github.com/jordanruthe/KlipperScreen.git
|
||||
#env: /home/%USER%/.KlipperScreen-env/bin/python
|
||||
#requirements: scripts/KlipperScreen-requirements.txt
|
||||
#install_script: scripts/KlipperScreen-install.sh
|
||||
|
||||
1
resources/moonraker.env
Normal file
1
resources/moonraker.env
Normal file
@@ -0,0 +1 @@
|
||||
MOONRAKER_ARGS="%MOONRAKER_DIR%/moonraker/moonraker.py -d %PRINTER_DATA%"
|
||||
@@ -1,16 +1,19 @@
|
||||
#Systemd service file for moonraker
|
||||
[Unit]
|
||||
Description=Starts Moonraker %INST% on startup
|
||||
After=network.target
|
||||
Description=API Server for Klipper SV1 %INST%
|
||||
Documentation=https://moonraker.readthedocs.io/
|
||||
Requires=network-online.target
|
||||
After=network-online.target
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
SupplementaryGroups=moonraker-admin
|
||||
User=%USER%
|
||||
SupplementaryGroups=moonraker-admin
|
||||
RemainAfterExit=yes
|
||||
ExecStart=%MR_ENV%/bin/python %MR_DIR%/moonraker/moonraker.py -l %MR_LOG% -c %MR_CONF%
|
||||
WorkingDirectory=%MOONRAKER_DIR%
|
||||
EnvironmentFile=%ENV_FILE%
|
||||
ExecStart=%ENV%/bin/python $MOONRAKER_ARGS
|
||||
Restart=always
|
||||
RestartSec=10
|
||||
@@ -1,40 +0,0 @@
|
||||
map $http_upgrade $connection_upgrade {
|
||||
default upgrade;
|
||||
'' close;
|
||||
}
|
||||
|
||||
upstream octoprint {
|
||||
server 127.0.0.1:5000;
|
||||
}
|
||||
|
||||
upstream mjpg-streamer {
|
||||
server 127.0.0.1:8080;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
#listen [::]:80;
|
||||
|
||||
location / {
|
||||
proxy_pass http://octoprint/;
|
||||
proxy_set_header Host $http_host;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
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;
|
||||
#proxy_set_header X-Script-Name /;
|
||||
proxy_http_version 1.1;
|
||||
client_max_body_size 0;
|
||||
}
|
||||
|
||||
location /webcam {
|
||||
proxy_pass http://mjpg-streamer/;
|
||||
}
|
||||
|
||||
# redirect server error pages to the static page /50x.html
|
||||
error_page 500 502 503 504 /50x.html;
|
||||
location = /50x.html {
|
||||
root html;
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 18 KiB |
BIN
resources/screenshots/rpi_imager1.png
Normal file
BIN
resources/screenshots/rpi_imager1.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 49 KiB |
BIN
resources/screenshots/rpi_imager2.png
Normal file
BIN
resources/screenshots/rpi_imager2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 58 KiB |
@@ -1,195 +1,193 @@
|
||||
check_for_backup_dir(){
|
||||
if [ ! -d $BACKUP_DIR ]; then
|
||||
#!/usr/bin/env bash
|
||||
|
||||
#=======================================================================#
|
||||
# Copyright (C) 2020 - 2023 Dominik Willner <th33xitus@gmail.com> #
|
||||
# #
|
||||
# This file is part of KIAUH - Klipper Installation And Update Helper #
|
||||
# https://github.com/dw-0/kiauh #
|
||||
# #
|
||||
# This file may be distributed under the terms of the GNU GPLv3 license #
|
||||
#=======================================================================#
|
||||
|
||||
set -e
|
||||
|
||||
function get_date() {
|
||||
local current_date
|
||||
current_date=$(date +"%y%m%d-%H%M")
|
||||
echo "${current_date}"
|
||||
}
|
||||
|
||||
function check_for_backup_dir() {
|
||||
[[ -d ${BACKUP_DIR} ]] && return
|
||||
|
||||
status_msg "Create KIAUH backup directory ..."
|
||||
mkdir -p $BACKUP_DIR && ok_msg "Directory created!"
|
||||
fi
|
||||
mkdir -p "${BACKUP_DIR}" && ok_msg "Directory created!"
|
||||
}
|
||||
|
||||
toggle_backups(){
|
||||
source_kiauh_ini
|
||||
if [ "$backup_before_update" = "true" ]; then
|
||||
sed -i '/backup_before_update=/s/true/false/' $INI_FILE
|
||||
BB4U_STATUS="${green}[Enable]${default} backups before updating "
|
||||
CONFIRM_MSG=" Backups before updates are now >>> DISABLED <<< !"
|
||||
fi
|
||||
if [ "$backup_before_update" = "false" ]; then
|
||||
sed -i '/backup_before_update=/s/false/true/' $INI_FILE
|
||||
BB4U_STATUS="${red}[Disable]${default} backups before updating "
|
||||
CONFIRM_MSG=" Backups before updates are now >>> ENABLED <<< !"
|
||||
fi
|
||||
function backup_before_update() {
|
||||
read_kiauh_ini "${FUNCNAME[0]}"
|
||||
local state="${backup_before_update}"
|
||||
[[ ${state} = "false" ]] && return
|
||||
backup_"${1}"
|
||||
}
|
||||
|
||||
bb4u(){
|
||||
source_kiauh_ini
|
||||
if [ "$backup_before_update" = "true" ]; then
|
||||
backup_$1
|
||||
fi
|
||||
}
|
||||
|
||||
read_bb4u_stat(){
|
||||
source_kiauh_ini
|
||||
if [ ! "$backup_before_update" = "true" ]; then
|
||||
BB4U_STATUS="${green}[Enable]${default} backups before updating "
|
||||
else
|
||||
BB4U_STATUS="${red}[Disable]${default} backups before updating "
|
||||
fi
|
||||
}
|
||||
|
||||
################################################################################
|
||||
#******************************************************************************#
|
||||
################################################################################
|
||||
|
||||
backup_printer_cfg(){
|
||||
function backup_config_dir() {
|
||||
check_for_backup_dir
|
||||
if [ -f $PRINTER_CFG ]; then
|
||||
get_date
|
||||
status_msg "Timestamp: $current_date"
|
||||
status_msg "Create backup of printer.cfg ..."
|
||||
cp $PRINTER_CFG $BACKUP_DIR/printer.cfg."$current_date".backup && ok_msg "Backup complete!"
|
||||
else
|
||||
ok_msg "No printer.cfg found! Skipping backup ..."
|
||||
fi
|
||||
}
|
||||
local current_date instance_names config_pathes
|
||||
|
||||
backup_klipper_config_dir(){
|
||||
source_kiauh_ini
|
||||
check_for_backup_dir
|
||||
if [ -d "$klipper_cfg_loc" ]; then
|
||||
get_date
|
||||
status_msg "Timestamp: $current_date"
|
||||
status_msg "Create backup of the Klipper config directory ..."
|
||||
config_folder_name="$(echo "$klipper_cfg_loc" | rev | cut -d"/" -f1 | rev)"
|
||||
mkdir -p $BACKUP_DIR/$config_folder_name/$current_date
|
||||
cp -r "$klipper_cfg_loc" "$_" && ok_msg "Backup complete!"
|
||||
echo
|
||||
config_pathes=$(get_config_folders)
|
||||
readarray -t -d" " instance_names < <(get_multi_instance_names)
|
||||
|
||||
if [[ -n "${config_pathes}" ]]; then
|
||||
current_date=$(get_date)
|
||||
status_msg "Timestamp: ${current_date}"
|
||||
|
||||
local i=0 folder
|
||||
for folder in ${config_pathes}; do
|
||||
local folder_name="${instance_names[${i}]}"
|
||||
status_msg "Create backup of ${folder} ..."
|
||||
mkdir -p "${BACKUP_DIR}/configs/${current_date}/${folder_name}"
|
||||
cp -r "${folder}" "${_}"
|
||||
ok_msg "Backup created in:\n${BACKUP_DIR}/configs/${current_date}/${folder_name}"
|
||||
i=$(( i + 1 ))
|
||||
done
|
||||
else
|
||||
ok_msg "No config directory found! Skipping backup ..."
|
||||
echo
|
||||
fi
|
||||
}
|
||||
|
||||
backup_moonraker_database(){
|
||||
function backup_moonraker_database() {
|
||||
check_for_backup_dir
|
||||
if ls -d ${HOME}/.moonraker_database* 2>/dev/null 1>&2; then
|
||||
get_date
|
||||
status_msg "Timestamp: $current_date"
|
||||
mkdir -p "$BACKUP_DIR/mr_db_backup/$current_date"
|
||||
for database in $(ls -d ${HOME}/.moonraker_database*)
|
||||
do
|
||||
status_msg "Create backup of $database ..."
|
||||
cp -r $database "$BACKUP_DIR/mr_db_backup/$current_date"
|
||||
ok_msg "Done!"
|
||||
local current_date db_pathes
|
||||
|
||||
db_pathes=$(get_instance_folder_path "database")
|
||||
readarray -t -d" " instance_names < <(get_multi_instance_names)
|
||||
|
||||
if [[ -n ${db_pathes} ]]; then
|
||||
current_date=$(get_date)
|
||||
status_msg "Timestamp: ${current_date}"
|
||||
|
||||
local i=0 database
|
||||
for database in ${db_pathes}; do
|
||||
local folder_name="${instance_names[${i}]}"
|
||||
status_msg "Create backup of ${database} ..."
|
||||
mkdir -p "${BACKUP_DIR}/moonraker_databases/${current_date}/${folder_name}"
|
||||
cp -r "${database}" "${_}"
|
||||
ok_msg "Backup created in:\n${BACKUP_DIR}/moonraker_databases/${current_date}/${folder_name}"
|
||||
i=$(( i + 1 ))
|
||||
done
|
||||
ok_msg "Backup complete!\n"
|
||||
else
|
||||
ok_msg "No Moonraker database found! Skipping backup ..."
|
||||
print_error "No Moonraker database found! Skipping backup ..."
|
||||
fi
|
||||
}
|
||||
|
||||
backup_klipper(){
|
||||
if [ -d $KLIPPER_DIR ] && [ -d $KLIPPY_ENV ]; then
|
||||
function backup_klipper() {
|
||||
local current_date
|
||||
|
||||
if [[ -d ${KLIPPER_DIR} && -d ${KLIPPY_ENV} ]]; then
|
||||
status_msg "Creating Klipper backup ..."
|
||||
check_for_backup_dir
|
||||
get_date
|
||||
status_msg "Timestamp: $current_date"
|
||||
mkdir -p $BACKUP_DIR/klipper-backups/"$current_date"
|
||||
cp -r $KLIPPER_DIR $_ && cp -r $KLIPPY_ENV $_ && ok_msg "Backup complete!"
|
||||
current_date=$(get_date)
|
||||
status_msg "Timestamp: ${current_date}"
|
||||
mkdir -p "${BACKUP_DIR}/klipper-backups/${current_date}"
|
||||
cp -r "${KLIPPER_DIR}" "${_}" && cp -r "${KLIPPY_ENV}" "${_}"
|
||||
print_confirm "Klipper backup complete!"
|
||||
else
|
||||
ERROR_MSG=" Can't backup klipper and/or klipper-env directory! Not found!"
|
||||
print_error "Can't back up 'klipper' and/or 'klipper-env' directory! Not found!"
|
||||
fi
|
||||
}
|
||||
|
||||
backup_dwc2(){
|
||||
if [ -d $DWC2FK_DIR ] && [ -d $DWC_ENV_DIR ] && [ -d $DWC2_DIR ]; then
|
||||
status_msg "Creating DWC2 Web UI backup ..."
|
||||
check_for_backup_dir
|
||||
get_date
|
||||
status_msg "Timestamp: $current_date"
|
||||
mkdir -p $BACKUP_DIR/dwc2-backups/"$current_date"
|
||||
cp -r $DWC2FK_DIR $_ && cp -r $DWC_ENV_DIR $_ && cp -r $DWC2_DIR $_
|
||||
ok_msg "Backup complete!"
|
||||
else
|
||||
ERROR_MSG=" Can't backup dwc2-for-klipper-socket and/or dwc2 directory!\n Not found!"
|
||||
fi
|
||||
}
|
||||
function backup_mainsail() {
|
||||
local current_date
|
||||
|
||||
backup_mainsail(){
|
||||
if [ -d $MAINSAIL_DIR ]; then
|
||||
if [[ -d ${MAINSAIL_DIR} ]]; then
|
||||
status_msg "Creating Mainsail backup ..."
|
||||
check_for_backup_dir
|
||||
get_date
|
||||
status_msg "Timestamp: $current_date"
|
||||
mkdir -p $BACKUP_DIR/mainsail-backups/"$current_date"
|
||||
cp -r $MAINSAIL_DIR $_ && ok_msg "Backup complete!"
|
||||
current_date=$(get_date)
|
||||
status_msg "Timestamp: ${current_date}"
|
||||
mkdir -p "${BACKUP_DIR}/mainsail-backups/${current_date}"
|
||||
cp -r "${MAINSAIL_DIR}" "${_}"
|
||||
print_confirm "Mainsail backup complete!"
|
||||
else
|
||||
ERROR_MSG=" Can't backup mainsail directory! Not found!"
|
||||
print_error "Can't back up 'mainsail' directory! Not found!"
|
||||
fi
|
||||
}
|
||||
|
||||
backup_fluidd(){
|
||||
if [ -d $FLUIDD_DIR ]; then
|
||||
function backup_fluidd() {
|
||||
local current_date
|
||||
|
||||
if [[ -d ${FLUIDD_DIR} ]]; then
|
||||
status_msg "Creating Fluidd backup ..."
|
||||
check_for_backup_dir
|
||||
get_date
|
||||
status_msg "Timestamp: $current_date"
|
||||
mkdir -p $BACKUP_DIR/fluidd-backups/"$current_date"
|
||||
cp -r $FLUIDD_DIR $_ && ok_msg "Backup complete!"
|
||||
current_date=$(get_date)
|
||||
status_msg "Timestamp: ${current_date}"
|
||||
mkdir -p "${BACKUP_DIR}/fluidd-backups/${current_date}"
|
||||
cp -r "${FLUIDD_DIR}" "${_}"
|
||||
print_confirm "Fluidd backup complete!"
|
||||
else
|
||||
ERROR_MSG=" Can't backup fluidd directory! Not found!"
|
||||
print_error "Can't back up 'fluidd' directory! Not found!"
|
||||
fi
|
||||
}
|
||||
|
||||
backup_moonraker(){
|
||||
if [ -d $MOONRAKER_DIR ] && [ -d $MOONRAKER_ENV ]; then
|
||||
function backup_moonraker() {
|
||||
local current_date
|
||||
|
||||
if [[ -d ${MOONRAKER_DIR} && -d ${MOONRAKER_ENV} ]]; then
|
||||
status_msg "Creating Moonraker backup ..."
|
||||
check_for_backup_dir
|
||||
get_date
|
||||
status_msg "Timestamp: $current_date"
|
||||
mkdir -p $BACKUP_DIR/moonraker-backups/"$current_date"
|
||||
cp -r $MOONRAKER_DIR $_ && cp -r $MOONRAKER_ENV $_ && ok_msg "Backup complete!"
|
||||
current_date=$(get_date)
|
||||
status_msg "Timestamp: ${current_date}"
|
||||
mkdir -p "${BACKUP_DIR}/moonraker-backups/${current_date}"
|
||||
cp -r "${MOONRAKER_DIR}" "${_}" && cp -r "${MOONRAKER_ENV}" "${_}"
|
||||
print_confirm "Moonraker backup complete!"
|
||||
else
|
||||
ERROR_MSG=" Can't backup moonraker and/or moonraker-env directory! Not found!"
|
||||
print_error "Can't back up moonraker and/or moonraker-env directory! Not found!"
|
||||
fi
|
||||
}
|
||||
|
||||
backup_octoprint(){
|
||||
if [ -d $OCTOPRINT_DIR ] && [ -d $OCTOPRINT_CFG_DIR ]; then
|
||||
function backup_octoprint() {
|
||||
local current_date
|
||||
|
||||
if [[ -d ${OCTOPRINT_DIR} && -d ${OCTOPRINT_CFG_DIR} ]]; then
|
||||
status_msg "Creating OctoPrint backup ..."
|
||||
check_for_backup_dir
|
||||
get_date
|
||||
status_msg "Timestamp: $current_date"
|
||||
mkdir -p $BACKUP_DIR/octoprint-backups/"$current_date"
|
||||
cp -r $OCTOPRINT_DIR $_ && cp -r $OCTOPRINT_CFG_DIR $_
|
||||
ok_msg "Backup complete!"
|
||||
current_date=$(get_date)
|
||||
status_msg "Timestamp: ${current_date}"
|
||||
mkdir -p "${BACKUP_DIR}/octoprint-backups/${current_date}"
|
||||
cp -r "${OCTOPRINT_DIR}" "${_}" && cp -r "${OCTOPRINT_CFG_DIR}" "${_}"
|
||||
print_confirm " OctoPrint backup complete!"
|
||||
else
|
||||
ERROR_MSG=" Can't backup OctoPrint and/or .octoprint directory!\n Not found!"
|
||||
print_error "Can't back up OctoPrint and/or .octoprint directory!\n Not found!"
|
||||
fi
|
||||
}
|
||||
|
||||
backup_klipperscreen(){
|
||||
if [ -d $KLIPPERSCREEN_DIR ] ; then
|
||||
function backup_klipperscreen() {
|
||||
local current_date
|
||||
if [[ -d ${KLIPPERSCREEN_DIR} ]] ; then
|
||||
status_msg "Creating KlipperScreen backup ..."
|
||||
check_for_backup_dir
|
||||
get_date
|
||||
status_msg "Timestamp: $current_date"
|
||||
mkdir -p $BACKUP_DIR/klipperscreen-backups/"$current_date"
|
||||
cp -r $KLIPPERSCREEN_DIR $_
|
||||
ok_msg "Backup complete!"
|
||||
current_date=$(get_date)
|
||||
status_msg "Timestamp: ${current_date}"
|
||||
mkdir -p "${BACKUP_DIR}/klipperscreen-backups/${current_date}"
|
||||
cp -r "${KLIPPERSCREEN_DIR}" "${_}"
|
||||
print_confirm "KlipperScreen backup complete!"
|
||||
else
|
||||
ERROR_MSG=" Can't backup KlipperScreen directory!\n Not found!"
|
||||
print_error "Can't back up KlipperScreen directory!\n Not found!"
|
||||
fi
|
||||
}
|
||||
|
||||
backup_MoonrakerTelegramBot(){
|
||||
if [ -d $MOONRAKER_TELEGRAM_BOT_DIR ] ; then
|
||||
function backup_telegram_bot() {
|
||||
local current_date
|
||||
|
||||
if [[ -d ${TELEGRAM_BOT_DIR} ]] ; then
|
||||
status_msg "Creating MoonrakerTelegramBot backup ..."
|
||||
check_for_backup_dir
|
||||
get_date
|
||||
status_msg "Timestamp: $current_date"
|
||||
mkdir -p $BACKUP_DIR/MoonrakerTelegramBot-backups/"$current_date"
|
||||
cp -r $MOONRAKER_TELEGRAM_BOT_DIR $_
|
||||
ok_msg "Backup complete!"
|
||||
current_date=$(get_date)
|
||||
status_msg "Timestamp: ${current_date}"
|
||||
mkdir -p "${BACKUP_DIR}/MoonrakerTelegramBot-backups/${current_date}"
|
||||
cp -r "${TELEGRAM_BOT_DIR}" "${_}"
|
||||
print_confirm "MoonrakerTelegramBot backup complete!"
|
||||
else
|
||||
ERROR_MSG=" Can't backup MoonrakerTelegramBot directory!\n Not found!"
|
||||
print_error "Can't back up MoonrakerTelegramBot directory!\n Not found!"
|
||||
fi
|
||||
}
|
||||
|
||||
235
scripts/crowsnest.sh
Normal file
235
scripts/crowsnest.sh
Normal file
@@ -0,0 +1,235 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
#=======================================================================#
|
||||
# Copyright (C) 2020 - 2023 Dominik Willner <th33xitus@gmail.com> #
|
||||
# #
|
||||
# This file is part of KIAUH - Klipper Installation And Update Helper #
|
||||
# https://github.com/dw-0/kiauh #
|
||||
# #
|
||||
# This file may be distributed under the terms of the GNU GPLv3 license #
|
||||
#=======================================================================#
|
||||
|
||||
#=======================================================================#
|
||||
# Crowsnest Installer brought to you by KwadFan <me@stephanwe.de> #
|
||||
# Copyright (C) 2022 KwadFan <me@stephanwe.de> #
|
||||
# https://github.com/KwadFan/crowsnest #
|
||||
#=======================================================================#
|
||||
|
||||
# Error Handling
|
||||
set -e
|
||||
|
||||
# Helper messages
|
||||
|
||||
function multi_instance_message(){
|
||||
echo -e "Crowsnest is NOT designed to support multi instances."
|
||||
echo -e "A workaround for this is to choose the most used instance as a 'master'"
|
||||
echo -e "Use this instance to set up your 'crowsnest.conf' and steering it's service.\n"
|
||||
echo -e "Found the following instances:\n"
|
||||
for i in ${1}; do
|
||||
select_msg "${i}"
|
||||
done
|
||||
echo -e "\nLaunching crowsnest's configuration tool ..."
|
||||
continue_config
|
||||
}
|
||||
|
||||
# Helper funcs
|
||||
function clone_crowsnest(){
|
||||
$(command -v git) clone "${CROWSNEST_REPO}" -b master "${CROWSNEST_DIR}"
|
||||
}
|
||||
|
||||
function check_multi_instance(){
|
||||
local -a instances
|
||||
readarray -t instances < <(find "${HOME}" -regex "${HOME}/[a-zA-Z0-9_]+_data/*" -printf "%P\n" 2> /dev/null | sort)
|
||||
if [[ "${#instances[@]}" -gt 1 ]]; then
|
||||
status_msg "Multi instance install detected ..."
|
||||
multi_instance_message "${instances[*]}"
|
||||
if [[ -d "${HOME}/crowsnest" ]]; then
|
||||
pushd "${HOME}/crowsnest" &> /dev/null || exit 1
|
||||
if ! make config ;then
|
||||
error_msg "Something went wrong! Please try again..."
|
||||
if [[ -f "tools/.config" ]]; then
|
||||
rm -f tools/.config
|
||||
fi
|
||||
exit 1
|
||||
fi
|
||||
if [[ ! -f "tools/.config" ]]; then
|
||||
log_error "failure while generating .config"
|
||||
error_msg "Generating .config failed, installation aborted"
|
||||
exit 1
|
||||
fi
|
||||
popd &> /dev/null || exit 1
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
function continue_config() {
|
||||
local reply
|
||||
while true; do
|
||||
read -erp "${cyan}###### Continue with configuration? (y/N):${white} " reply
|
||||
case "${reply}" in
|
||||
Y|y|Yes|yes)
|
||||
select_msg "Yes"
|
||||
break;;
|
||||
N|n|No|no|"")
|
||||
select_msg "No"
|
||||
warn_msg "Installation aborted by user ... Exiting!"
|
||||
exit 1;;
|
||||
*)
|
||||
error_msg "Invalid Input!\n";;
|
||||
esac
|
||||
done
|
||||
return 0
|
||||
}
|
||||
|
||||
# Install func
|
||||
function install_crowsnest(){
|
||||
|
||||
# Step 1: jump to home directory
|
||||
pushd "${HOME}" &> /dev/null || exit 1
|
||||
|
||||
# Step 2: Clone crowsnest repo
|
||||
status_msg "Cloning 'crowsnest' repository ..."
|
||||
if [[ ! -d "${HOME}/crowsnest" && -z "$(ls -A "${HOME}/crowsnest" 2> /dev/null)" ]]; then
|
||||
clone_crowsnest
|
||||
else
|
||||
ok_msg "crowsnest repository already exists ..."
|
||||
fi
|
||||
|
||||
# Step 3: Install dependencies
|
||||
dependency_check git make
|
||||
|
||||
# Step 4: Check for Multi Instance
|
||||
check_multi_instance
|
||||
|
||||
# Step 5: Launch crowsnest installer
|
||||
pushd "${HOME}/crowsnest" &> /dev/null || exit 1
|
||||
title_msg "Installer will prompt you for sudo password!"
|
||||
status_msg "Launching crowsnest installer ..."
|
||||
if ! sudo make install BASE_USER=$USER; then
|
||||
error_msg "Something went wrong! Please try again..."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Step 5: Leave directory (twice due two pushd)
|
||||
popd &> /dev/null || exit 1
|
||||
popd &> /dev/null || exit 1
|
||||
}
|
||||
|
||||
# Remove func
|
||||
function remove_crowsnest(){
|
||||
if [[ -d "${CROWSNEST_DIR}" ]]; then
|
||||
pushd "${HOME}/crowsnest" &> /dev/null || exit 1
|
||||
title_msg "Uninstaller will prompt you for sudo password!"
|
||||
status_msg "Launching crowsnest uninstaller ..."
|
||||
|
||||
if ! make uninstall; then
|
||||
error_msg "Something went wrong! Please try again..."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
status_msg "Removing crowsnest directory ..."
|
||||
rm -rf "${CROWSNEST_DIR}"
|
||||
ok_msg "Directory removed!"
|
||||
fi
|
||||
|
||||
print_confirm "Crowsnest successfully removed!"
|
||||
}
|
||||
|
||||
# Status funcs
|
||||
get_crowsnest_status(){
|
||||
local -a files
|
||||
local env_file
|
||||
env_file="$(grep "EnvironmentFile" /etc/systemd/system/crowsnest.service 2>/dev/null | cut -d "=" -f2)"
|
||||
files=(
|
||||
"${CROWSNEST_DIR}"
|
||||
"/usr/local/bin/crowsnest"
|
||||
"/etc/logrotate.d/crowsnest"
|
||||
"/etc/systemd/system/crowsnest.service"
|
||||
"${env_file}"
|
||||
)
|
||||
local count
|
||||
count=0
|
||||
|
||||
for file in "${files[@]}"; do
|
||||
[[ -e "${file}" ]] && count=$(( count +1 ))
|
||||
done
|
||||
if [[ "${count}" -eq "${#files[*]}" ]]; then
|
||||
echo "Installed"
|
||||
elif [[ "${count}" -gt 0 ]]; then
|
||||
echo "Incomplete!"
|
||||
else
|
||||
echo "Not installed!"
|
||||
fi
|
||||
}
|
||||
|
||||
# Update funcs
|
||||
# Shameless stolen from KlipperScreen.sh
|
||||
function get_local_crowsnest_commit() {
|
||||
[[ ! -d ${CROWSNEST_DIR} || ! -d "${CROWSNEST_DIR}/.git" ]] && return
|
||||
|
||||
local commit
|
||||
cd "${CROWSNEST_DIR}"
|
||||
commit="$(git describe HEAD --always --tags | cut -d "-" -f 1,2)"
|
||||
echo "${commit}"
|
||||
}
|
||||
|
||||
function get_remote_crowsnest_commit() {
|
||||
[[ ! -d ${CROWSNEST_DIR} || ! -d "${CROWSNEST_DIR}/.git" ]] && return
|
||||
|
||||
local commit
|
||||
cd "${CROWSNEST_DIR}" && git fetch origin -q
|
||||
commit=$(git describe origin/master --always --tags | cut -d "-" -f 1,2)
|
||||
echo "${commit}"
|
||||
}
|
||||
|
||||
function compare_crowsnest_versions() {
|
||||
local versions local_ver remote_ver
|
||||
local_ver="$(get_local_crowsnest_commit)"
|
||||
remote_ver="$(get_remote_crowsnest_commit)"
|
||||
|
||||
if [[ ${local_ver} != "${remote_ver}" ]]; then
|
||||
versions="${yellow}$(printf " %-14s" "${local_ver}")${white}"
|
||||
versions+="|${green}$(printf " %-13s" "${remote_ver}")${white}"
|
||||
# add moonraker to application_updates_available in kiauh.ini
|
||||
add_to_application_updates "crowsnest"
|
||||
else
|
||||
versions="${green}$(printf " %-14s" "${local_ver}")${white}"
|
||||
versions+="|${green}$(printf " %-13s" "${remote_ver}")${white}"
|
||||
fi
|
||||
|
||||
echo "${versions}"
|
||||
}
|
||||
|
||||
function install_crowsnest_dependencies() {
|
||||
local packages log_name="Crowsnest"
|
||||
local install_script="${CROWSNEST_DIR}/tools/install.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 lists if stale
|
||||
update_system_package_lists
|
||||
|
||||
### Install required packages
|
||||
install_system_packages "${log_name}" "packages[@]"
|
||||
}
|
||||
|
||||
function update_crowsnest() {
|
||||
do_action_service "stop" "crowsnest"
|
||||
|
||||
if [[ ! -d ${CROWSNEST_DIR} ]]; then
|
||||
clone_crowsnest
|
||||
else
|
||||
status_msg "Updating Crowsnest ..."
|
||||
cd "${CROWSNEST_DIR}" && git pull
|
||||
### read PKGLIST and install possible new dependencies
|
||||
install_crowsnest_dependencies
|
||||
fi
|
||||
ok_msg "Update complete!"
|
||||
do_action_service "restart" "crowsnest"
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
#!/bin/sh
|
||||
# System startup script for dwc2-for-klipper-socket
|
||||
|
||||
### BEGIN INIT INFO
|
||||
# Provides: dwc2-for-klipper-socket
|
||||
# Required-Start: $local_fs
|
||||
# Required-Stop:
|
||||
# Default-Start: 2 3 4 5
|
||||
# Default-Stop: 0 1 6
|
||||
# Short-Description: dwc2-for-klipper-socket daemon
|
||||
# Description: Starts the dwc2-for-klipper-socket daemon.
|
||||
### END INIT INFO
|
||||
|
||||
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
|
||||
DESC="dwc2-for-klipper-socket daemon"
|
||||
NAME="dwc2-for-klipper-socket"
|
||||
DEFAULTS_FILE=/etc/default/dwc
|
||||
PIDFILE=/var/run/dwc.pid
|
||||
|
||||
. /lib/lsb/init-functions
|
||||
|
||||
# Read defaults file
|
||||
[ -r $DEFAULTS_FILE ] && . $DEFAULTS_FILE
|
||||
|
||||
case "$1" in
|
||||
start) log_daemon_msg "Starting dwc2-for-klipper-socket" $NAME
|
||||
start-stop-daemon --start --quiet --exec $DWC_EXEC \
|
||||
--background --pidfile $PIDFILE --make-pidfile \
|
||||
--chuid $DWC_USER --user $DWC_USER \
|
||||
-- $DWC_ARGS
|
||||
log_end_msg $?
|
||||
;;
|
||||
stop) log_daemon_msg "Stopping dwc2-for-klipper-socket" $NAME
|
||||
killproc -p $PIDFILE $DWC_EXEC
|
||||
RETVAL=$?
|
||||
[ $RETVAL -eq 0 ] && [ -e "$PIDFILE" ] && rm -f $PIDFILE
|
||||
log_end_msg $RETVAL
|
||||
;;
|
||||
restart) log_daemon_msg "Restarting dwc2-for-klipper-socket" $NAME
|
||||
$0 stop
|
||||
$0 start
|
||||
;;
|
||||
reload|force-reload)
|
||||
log_daemon_msg "Reloading configuration not supported" $NAME
|
||||
log_end_msg 1
|
||||
;;
|
||||
status)
|
||||
status_of_proc -p $PIDFILE $DWC_EXEC $NAME && exit 0 || exit $?
|
||||
;;
|
||||
*) log_action_msg "Usage: /etc/init.d/dwc {start|stop|status|restart|reload|force-reload}"
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
exit 0
|
||||
@@ -1,106 +0,0 @@
|
||||
#!/bin/bash
|
||||
# This script installs dwc2-for-klipper-socket on a Raspberry Pi machine running
|
||||
# Raspbian/Raspberry Pi OS based distributions.
|
||||
|
||||
# https://github.com/Stephan3/dwc2-for-klipper-socket.git
|
||||
|
||||
PYTHONDIR="${HOME}/dwc-env"
|
||||
SYSTEMDDIR="/etc/systemd/system"
|
||||
DWC_USER=${USER}
|
||||
|
||||
# Step 1: Verify Klipper has been installed
|
||||
check_klipper()
|
||||
{
|
||||
if [ "$(systemctl list-units --full -all -t service --no-legend | grep -F "klipper.service")" ]; then
|
||||
echo "Klipper service found!"
|
||||
else
|
||||
echo "Klipper service not found, please install Klipper first"
|
||||
exit -1
|
||||
fi
|
||||
|
||||
}
|
||||
|
||||
# Step 2: Install packages
|
||||
install_packages()
|
||||
{
|
||||
PKGLIST="python3-virtualenv python3-dev python3-tornado"
|
||||
|
||||
# Update system package info
|
||||
report_status "Running apt-get update..."
|
||||
sudo apt-get update --allow-releaseinfo-change
|
||||
|
||||
# Install desired packages
|
||||
report_status "Installing packages..."
|
||||
sudo apt-get install --yes ${PKGLIST}
|
||||
}
|
||||
|
||||
# Step 3: Create python virtual environment
|
||||
create_virtualenv()
|
||||
{
|
||||
report_status "Updating python virtual environment..."
|
||||
|
||||
# Create virtualenv if it doesn't already exist
|
||||
[ ! -d ${PYTHONDIR} ] && virtualenv -p /usr/bin/python3 ${PYTHONDIR}
|
||||
|
||||
# Install/update dependencies
|
||||
${PYTHONDIR}/bin/pip install tornado==6.0.4
|
||||
}
|
||||
|
||||
# Step 4: Install startup script
|
||||
install_script(){
|
||||
report_status "Installing system start script..."
|
||||
sudo /bin/sh -c "cat > $SYSTEMDDIR/dwc.service" << EOF
|
||||
#Systemd service file for DWC
|
||||
[Unit]
|
||||
Description=dwc_webif
|
||||
After=network.target
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=$DWC_USER
|
||||
RemainAfterExit=yes
|
||||
ExecStart=${PYTHONDIR}/bin/python3 ${SRCDIR}/web_dwc2.py
|
||||
Restart=always
|
||||
RestartSec=10
|
||||
EOF
|
||||
# Use systemctl to enable the klipper systemd service script
|
||||
sudo systemctl enable dwc.service
|
||||
}
|
||||
|
||||
# Step 5: Start DWC service
|
||||
start_software()
|
||||
{
|
||||
report_status "Launching dwc2-for-klipper-socket..."
|
||||
sudo systemctl start dwc
|
||||
}
|
||||
|
||||
# Helper functions
|
||||
report_status()
|
||||
{
|
||||
echo -e "\n\n###### $1"
|
||||
}
|
||||
|
||||
verify_ready()
|
||||
{
|
||||
if [ "$EUID" -eq 0 ]; then
|
||||
echo "This script must not run as root"
|
||||
exit -1
|
||||
fi
|
||||
}
|
||||
|
||||
# Force script to exit if an error occurs
|
||||
set -e
|
||||
|
||||
# Find SRCDIR from the pathname of this script
|
||||
SRCDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )"/.. && pwd )"
|
||||
|
||||
# Run installation steps defined above
|
||||
verify_ready
|
||||
check_klipper
|
||||
install_packages
|
||||
create_virtualenv
|
||||
install_script
|
||||
start_software
|
||||
@@ -1,106 +0,0 @@
|
||||
#!/bin/bash
|
||||
# This script installs dwc2-for-klipper-socket on a Raspberry Pi machine running
|
||||
# Raspbian/Raspberry Pi OS based distributions.
|
||||
|
||||
# https://github.com/Stephan3/dwc2-for-klipper-socket.git
|
||||
|
||||
PYTHONDIR="${HOME}/dwc-env"
|
||||
|
||||
# Step 1: Verify Klipper has been installed
|
||||
check_klipper()
|
||||
{
|
||||
if [ "$(systemctl list-units --full -all -t service --no-legend | grep -F "klipper.service")" ]; then
|
||||
echo "Klipper service found!"
|
||||
else
|
||||
echo "Klipper service not found, please install Klipper first"
|
||||
exit -1
|
||||
fi
|
||||
|
||||
}
|
||||
|
||||
# Step 2: Install packages
|
||||
install_packages()
|
||||
{
|
||||
PKGLIST="python3-virtualenv python3-dev python3-tornado"
|
||||
|
||||
# Update system package info
|
||||
report_status "Running apt-get update..."
|
||||
sudo apt-get update --allow-releaseinfo-change
|
||||
|
||||
# Install desired packages
|
||||
report_status "Installing packages..."
|
||||
sudo apt-get install --yes ${PKGLIST}
|
||||
}
|
||||
|
||||
# Step 3: Create python virtual environment
|
||||
create_virtualenv()
|
||||
{
|
||||
report_status "Updating python virtual environment..."
|
||||
|
||||
# Create virtualenv if it doesn't already exist
|
||||
[ ! -d ${PYTHONDIR} ] && virtualenv -p /usr/bin/python3 ${PYTHONDIR}
|
||||
|
||||
# Install/update dependencies
|
||||
${PYTHONDIR}/bin/pip install tornado==6.0.4
|
||||
}
|
||||
|
||||
# Step 4: Install startup script
|
||||
install_script(){
|
||||
report_status "Installing system start script..."
|
||||
sudo cp "${SRCDIR}/scripts/dwc-start.sh" /etc/init.d/dwc
|
||||
sudo update-rc.d dwc defaults
|
||||
}
|
||||
|
||||
# Step 5: Install startup script config
|
||||
install_config(){
|
||||
DEFAULTS_FILE=/etc/default/dwc
|
||||
[ -f $DEFAULTS_FILE ] && return
|
||||
|
||||
report_status "Installing system start configuration..."
|
||||
sudo /bin/sh -c "cat > $DEFAULTS_FILE" <<EOF
|
||||
# Configuration for /etc/init.d/dwc
|
||||
DWC_USER=$USER
|
||||
|
||||
DWC_EXEC=${PYTHONDIR}/bin/python3
|
||||
|
||||
DWC_ARGS="${SRCDIR}/web_dwc2.py"
|
||||
EOF
|
||||
}
|
||||
|
||||
# Step 4: Start server
|
||||
start_software()
|
||||
{
|
||||
report_status "Launching dwc2-for-klipper-socket..."
|
||||
sudo /etc/init.d/klipper stop
|
||||
sudo /etc/init.d/dwc restart
|
||||
sudo /etc/init.d/klipper start
|
||||
}
|
||||
|
||||
# Helper functions
|
||||
report_status()
|
||||
{
|
||||
echo -e "\n\n###### $1"
|
||||
}
|
||||
|
||||
verify_ready()
|
||||
{
|
||||
if [ "$EUID" -eq 0 ]; then
|
||||
echo "This script must not run as root"
|
||||
exit -1
|
||||
fi
|
||||
}
|
||||
|
||||
# Force script to exit if an error occurs
|
||||
set -e
|
||||
|
||||
# Find SRCDIR from the pathname of this script
|
||||
SRCDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )"/.. && pwd )"
|
||||
|
||||
# Run installation steps defined above
|
||||
verify_ready
|
||||
check_klipper
|
||||
install_packages
|
||||
create_virtualenv
|
||||
install_script
|
||||
install_config
|
||||
start_software
|
||||
@@ -1,41 +0,0 @@
|
||||
stop_service() {
|
||||
# Stop DWC Service
|
||||
echo "#### Stopping DWC Service.."
|
||||
sudo systemctl stop dwc
|
||||
sudo systemctl disable dwc
|
||||
}
|
||||
|
||||
remove_service() {
|
||||
# Remove DWC from Services
|
||||
echo
|
||||
echo "#### Removing DWC Service.."
|
||||
sudo rm -f /etc/systemd/system/dwc.service
|
||||
sudo systemctl daemon-reload
|
||||
}
|
||||
|
||||
remove_files() {
|
||||
# Remove virtualenv
|
||||
if [ -d ~/dwc-env ]; then
|
||||
echo "Removing virtualenv..."
|
||||
rm -rf ~/dwc-env
|
||||
else
|
||||
echo "No DWC virtualenv found"
|
||||
fi
|
||||
|
||||
# Notify user of method to remove DWC source code
|
||||
echo
|
||||
echo "The DWC system files and virtualenv have been removed."
|
||||
}
|
||||
|
||||
verify_ready()
|
||||
{
|
||||
if [ "$EUID" -eq 0 ]; then
|
||||
echo "This script must not run as root"
|
||||
exit -1
|
||||
fi
|
||||
}
|
||||
|
||||
verify_ready
|
||||
stop_service
|
||||
remove_service
|
||||
remove_files
|
||||
@@ -1,45 +0,0 @@
|
||||
stop_service() {
|
||||
# Stop DWC Service
|
||||
echo "#### Stopping DWC Service.."
|
||||
sudo service dwc stop
|
||||
}
|
||||
|
||||
remove_service() {
|
||||
# Remove DWC from Startup
|
||||
echo
|
||||
echo "#### Removing DWC from Startup.."
|
||||
sudo update-rc.d -f dwc remove
|
||||
|
||||
# Remove DWC from Services
|
||||
echo
|
||||
echo "#### Removing DWC Service.."
|
||||
sudo rm -f /etc/init.d/dwc /etc/default/dwc
|
||||
|
||||
}
|
||||
|
||||
remove_files() {
|
||||
# Remove virtualenv
|
||||
if [ -d ~/dwc-env ]; then
|
||||
echo "Removing virtualenv..."
|
||||
rm -rf ~/dwc-env
|
||||
else
|
||||
echo "No DWC virtualenv found"
|
||||
fi
|
||||
|
||||
# Notify user of method to remove DWC source code
|
||||
echo
|
||||
echo "The DWC system files and virtualenv have been removed."
|
||||
}
|
||||
|
||||
verify_ready()
|
||||
{
|
||||
if [ "$EUID" -eq 0 ]; then
|
||||
echo "This script must not run as root"
|
||||
exit -1
|
||||
fi
|
||||
}
|
||||
|
||||
verify_ready
|
||||
stop_service
|
||||
remove_service
|
||||
remove_files
|
||||
424
scripts/flash_klipper.sh
Normal file
424
scripts/flash_klipper.sh
Normal file
@@ -0,0 +1,424 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
#=======================================================================#
|
||||
# Copyright (C) 2020 - 2023 Dominik Willner <th33xitus@gmail.com> #
|
||||
# #
|
||||
# This file is part of KIAUH - Klipper Installation And Update Helper #
|
||||
# https://github.com/dw-0/kiauh #
|
||||
# #
|
||||
# This file may be distributed under the terms of the GNU GPLv3 license #
|
||||
#=======================================================================#
|
||||
|
||||
set -e
|
||||
|
||||
function init_flash_process() {
|
||||
### step 1: check for required userhgroups (tty & dialout)
|
||||
check_usergroups
|
||||
|
||||
top_border
|
||||
echo -e "| ~~~~~~~~~~~~ [ Flash MCU ] ~~~~~~~~~~~~ |"
|
||||
hr
|
||||
echo -e "| Please select the flashing method to flash your MCU. |"
|
||||
echo -e "| Make sure to only select a method your MCU supports. |"
|
||||
echo -e "| Not all MCUs support both methods! |"
|
||||
hr
|
||||
blank_line
|
||||
echo -e "| 1) Regular flashing method |"
|
||||
echo -e "| 2) Updating via SD-Card Update |"
|
||||
blank_line
|
||||
back_help_footer
|
||||
|
||||
local choice method
|
||||
while true; do
|
||||
read -p "${cyan}###### Please select:${white} " choice
|
||||
case "${choice}" in
|
||||
1)
|
||||
select_msg "Regular flashing method"
|
||||
method="regular"
|
||||
break;;
|
||||
2)
|
||||
select_msg "SD-Card Update"
|
||||
method="sdcard"
|
||||
break;;
|
||||
B|b)
|
||||
advanced_menu
|
||||
break;;
|
||||
H|h)
|
||||
clear && print_header
|
||||
show_flash_method_help
|
||||
break;;
|
||||
*)
|
||||
error_msg "Invalid command!";;
|
||||
esac
|
||||
done
|
||||
|
||||
### step 2: select how the mcu is connected to the host
|
||||
select_mcu_connection
|
||||
|
||||
### step 3: select which detected mcu should be flashed
|
||||
select_mcu_id "${method}"
|
||||
}
|
||||
|
||||
#================================================#
|
||||
#=================== STEP 2 =====================#
|
||||
#================================================#
|
||||
function select_mcu_connection() {
|
||||
top_border
|
||||
echo -e "| ${yellow}Make sure that the controller board is connected now!${white} |"
|
||||
hr
|
||||
blank_line
|
||||
echo -e "| How is the controller board connected to the host? |"
|
||||
echo -e "| 1) USB |"
|
||||
echo -e "| 2) UART |"
|
||||
echo -e "| 3) USB (DFU mode) |"
|
||||
blank_line
|
||||
back_help_footer
|
||||
|
||||
local choice
|
||||
while true; do
|
||||
read -p "${cyan}###### Connection method:${white} " choice
|
||||
case "${choice}" in
|
||||
1)
|
||||
status_msg "Identifying MCU connected via USB ...\n"
|
||||
get_usb_id || true # continue even after exit code 1
|
||||
break;;
|
||||
2)
|
||||
status_msg "Identifying MCU possibly connected via UART ...\n"
|
||||
get_uart_id || true # continue even after exit code 1
|
||||
break;;
|
||||
3)
|
||||
status_msg "Identifying MCU connected via USB in DFU mode ...\n"
|
||||
get_dfu_id || true # continue even after exit code 1
|
||||
break;;
|
||||
B|b)
|
||||
advanced_menu
|
||||
break;;
|
||||
H|h)
|
||||
clear && print_header
|
||||
show_mcu_connection_help
|
||||
break;;
|
||||
*)
|
||||
error_msg "Invalid command!";;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
function print_detected_mcu_to_screen() {
|
||||
local i=1
|
||||
|
||||
if (( ${#mcu_list[@]} < 1 )); then
|
||||
print_error "No MCU found!\n MCU either not connected or not detected!"
|
||||
return
|
||||
fi
|
||||
|
||||
for mcu in "${mcu_list[@]}"; do
|
||||
mcu=$(echo "${mcu}" | rev | cut -d"/" -f1 | rev)
|
||||
echo -e " ● MCU #${i}: ${cyan}${mcu}${white}"
|
||||
i=$(( i + 1 ))
|
||||
done
|
||||
echo
|
||||
}
|
||||
|
||||
#================================================#
|
||||
#=================== STEP 3 =====================#
|
||||
#================================================#
|
||||
function select_mcu_id() {
|
||||
local i=0 sel_index=0 method=${1}
|
||||
|
||||
if (( ${#mcu_list[@]} < 1 )); then
|
||||
print_error "No MCU found!\n MCU either not connected or not detected!"
|
||||
return
|
||||
fi
|
||||
|
||||
top_border
|
||||
echo -e "| ${red}!!! ATTENTION !!!${white} |"
|
||||
hr
|
||||
echo -e "| Make sure, to select the correct MCU! |"
|
||||
echo -e "| ${red}ONLY flash a firmware created for the respective MCU!${white} |"
|
||||
bottom_border
|
||||
echo -e "${cyan}###### List of available MCU:${white}"
|
||||
|
||||
### list all mcus
|
||||
for mcu in "${mcu_list[@]}"; do
|
||||
i=$(( i + 1 ))
|
||||
mcu=$(echo "${mcu}" | rev | cut -d"/" -f1 | rev)
|
||||
echo -e " ● MCU #${i}: ${cyan}${mcu}${white}"
|
||||
done
|
||||
|
||||
### verify user input
|
||||
local regex="^[1-9]+$"
|
||||
while [[ ! ${sel_index} =~ ${regex} ]] || [[ ${sel_index} -gt ${i} ]]; do
|
||||
echo
|
||||
read -p "${cyan}###### Select MCU to flash:${white} " sel_index
|
||||
|
||||
if [[ ! ${sel_index} =~ ${regex} ]]; then
|
||||
error_msg "Invalid input!"
|
||||
elif [[ ${sel_index} -lt 1 ]] || [[ ${sel_index} -gt ${i} ]]; then
|
||||
error_msg "Please select a number between 1 and ${i}!"
|
||||
fi
|
||||
|
||||
local mcu_index=$(( sel_index - 1 ))
|
||||
local selected_mcu_id="${mcu_list[${mcu_index}]}"
|
||||
done
|
||||
|
||||
### confirm selection
|
||||
local yn
|
||||
while true; do
|
||||
echo -e "\n###### You selected:\n ● MCU #${sel_index}: ${selected_mcu_id}\n"
|
||||
read -p "${cyan}###### Continue? (Y/n):${white} " yn
|
||||
case "${yn}" in
|
||||
Y|y|Yes|yes|"")
|
||||
select_msg "Yes"
|
||||
status_msg "Flashing ${selected_mcu_id} ..."
|
||||
if [[ ${method} == "regular" ]]; then
|
||||
log_info "Flashing device '${selected_mcu_id}' with method '${method}'"
|
||||
start_flash_mcu "${selected_mcu_id}"
|
||||
elif [[ ${method} == "sdcard" ]]; then
|
||||
log_info "Flashing device '${selected_mcu_id}' with method '${method}'"
|
||||
start_flash_sd "${selected_mcu_id}"
|
||||
else
|
||||
print_error "No flash method set! Aborting..."
|
||||
log_error "No flash method set!"
|
||||
return
|
||||
fi
|
||||
break;;
|
||||
N|n|No|no)
|
||||
select_msg "No"
|
||||
break;;
|
||||
*)
|
||||
error_msg "Invalid command!";;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
function start_flash_mcu() {
|
||||
local device=${1}
|
||||
do_action_service "stop" "klipper"
|
||||
|
||||
if make flash FLASH_DEVICE="${device}"; then
|
||||
ok_msg "Flashing successfull!"
|
||||
else
|
||||
warn_msg "Flashing failed!"
|
||||
warn_msg "Please read the console output above!"
|
||||
fi
|
||||
|
||||
do_action_service "start" "klipper"
|
||||
}
|
||||
|
||||
function start_flash_sd() {
|
||||
local i=0 board_list=() device=${1}
|
||||
local flash_script="${KLIPPER_DIR}/scripts/flash-sdcard.sh"
|
||||
|
||||
### write each supported board to the array to make it selectable
|
||||
for board in $("${flash_script}" -l | tail -n +2); do
|
||||
board_list+=("${board}")
|
||||
done
|
||||
|
||||
top_border
|
||||
echo -e "| Please select the type of board that corresponds to |"
|
||||
echo -e "| the currently selected MCU ID you chose before. |"
|
||||
blank_line
|
||||
echo -e "| The following boards are currently supported: |"
|
||||
hr
|
||||
### display all supported boards to the user
|
||||
for board in "${board_list[@]}"; do
|
||||
if [[ ${i} -lt 10 ]]; then
|
||||
printf "| ${i}) %-50s|\n" "${board_list[${i}]}"
|
||||
else
|
||||
printf "| ${i}) %-49s|\n" "${board_list[${i}]}"
|
||||
fi
|
||||
i=$(( i + 1 ))
|
||||
done
|
||||
quit_footer
|
||||
|
||||
### make the user select one of the boards
|
||||
local choice
|
||||
while true; do
|
||||
read -p "${cyan}###### Please select board type:${white} " choice
|
||||
if [[ ${choice} = "q" || ${choice} = "Q" ]]; then
|
||||
clear && advanced_menu && break
|
||||
elif [[ ${choice} -le ${#board_list[@]} ]]; then
|
||||
local selected_board="${board_list[${choice}]}"
|
||||
break
|
||||
else
|
||||
clear && print_header
|
||||
error_msg "Invalid choice!"
|
||||
flash_mcu_sd
|
||||
fi
|
||||
done
|
||||
|
||||
while true; do
|
||||
echo
|
||||
top_border
|
||||
echo -e "| If your board is flashed with firmware that connects |"
|
||||
echo -e "| at a custom baud rate, please change it now. |"
|
||||
blank_line
|
||||
echo -e "| If you are unsure, stick to the default 250000! |"
|
||||
bottom_border
|
||||
|
||||
local baud_rate regex="^[0-9]+$"
|
||||
echo -e "${cyan}###### Please set the baud rate:${white} "
|
||||
while [[ ! ${baud_rate} =~ ${regex} ]]; do
|
||||
read -e -i "250000" -e baud_rate
|
||||
local selected_baud_rate=${baud_rate}
|
||||
break
|
||||
done
|
||||
break
|
||||
done
|
||||
|
||||
###flash process
|
||||
do_action_service "stop" "klipper"
|
||||
if "${flash_script}" -b "${selected_baud_rate}" "${device}" "${selected_board}"; then
|
||||
print_confirm "Flashing successfull!"
|
||||
log_info "Flash successfull!"
|
||||
else
|
||||
print_error "Flashing failed!\n Please read the console output above!"
|
||||
log_error "Flash failed!"
|
||||
fi
|
||||
do_action_service "start" "klipper"
|
||||
}
|
||||
|
||||
function build_fw() {
|
||||
local python_version
|
||||
|
||||
if [[ ! -d ${KLIPPER_DIR} || ! -d ${KLIPPY_ENV} ]]; then
|
||||
print_error "Klipper not found!\n Cannot build firmware without Klipper!"
|
||||
return
|
||||
fi
|
||||
|
||||
python_version=$(get_klipper_python_ver)
|
||||
|
||||
cd "${KLIPPER_DIR}"
|
||||
status_msg "Initializing firmware build ..."
|
||||
local dep=(build-essential dpkg-dev make)
|
||||
dependency_check "${dep[@]}"
|
||||
|
||||
make clean
|
||||
|
||||
status_msg "Building firmware ..."
|
||||
if (( python_version == 3 )); then
|
||||
make PYTHON=python3 menuconfig
|
||||
make PYTHON=python3
|
||||
elif (( python_version == 2 )); then
|
||||
make PYTHON=python2 menuconfig
|
||||
make PYTHON=python2
|
||||
else
|
||||
warn_msg "Error reading Python version!"
|
||||
return 1
|
||||
fi
|
||||
|
||||
ok_msg "Firmware built!"
|
||||
}
|
||||
|
||||
#================================================#
|
||||
#=================== HELPERS ====================#
|
||||
#================================================#
|
||||
|
||||
function get_usb_id() {
|
||||
unset mcu_list
|
||||
sleep 1
|
||||
mcus=$(find /dev/serial/by-id/* 2>/dev/null)
|
||||
|
||||
for mcu in ${mcus}; do
|
||||
mcu_list+=("${mcu}")
|
||||
done
|
||||
}
|
||||
|
||||
function get_uart_id() {
|
||||
unset mcu_list
|
||||
sleep 1
|
||||
mcus=$(find /dev -maxdepth 1 -regextype posix-extended -regex "^\/dev\/tty(AMA0|S0)$" 2>/dev/null)
|
||||
|
||||
for mcu in ${mcus}; do
|
||||
mcu_list+=("${mcu}")
|
||||
done
|
||||
}
|
||||
|
||||
function get_dfu_id() {
|
||||
unset mcu_list
|
||||
sleep 1
|
||||
mcus=$(lsusb | grep "DFU" | cut -d " " -f 6 2>/dev/null)
|
||||
|
||||
for mcu in ${mcus}; do
|
||||
mcu_list+=("${mcu}")
|
||||
done
|
||||
}
|
||||
|
||||
function show_flash_method_help() {
|
||||
top_border
|
||||
echo -e "| ~~~~~~~~ < ? > Help: Flash MCU < ? > ~~~~~~~~ |"
|
||||
hr
|
||||
echo -e "| ${cyan}Regular flashing method:${white} |"
|
||||
echo -e "| The default method to flash controller boards which |"
|
||||
echo -e "| are connected and updated over USB and not by placing |"
|
||||
echo -e "| a compiled firmware file onto an internal SD-Card. |"
|
||||
blank_line
|
||||
echo -e "| Common controllers that get flashed that way are: |"
|
||||
echo -e "| - Arduino Mega 2560 |"
|
||||
echo -e "| - Fysetc F6 / S6 (used without a Display + SD-Slot) |"
|
||||
blank_line
|
||||
echo -e "| ${cyan}Updating via SD-Card Update:${white} |"
|
||||
echo -e "| Many popular controller boards ship with a bootloader |"
|
||||
echo -e "| capable of updating the firmware via SD-Card. |"
|
||||
echo -e "| Choose this method if your controller board supports |"
|
||||
echo -e "| this way of updating. This method ONLY works for up- |"
|
||||
echo -e "| grading firmware. The initial flashing procedure must |"
|
||||
echo -e "| be done manually per the instructions that apply to |"
|
||||
echo -e "| your controller board. |"
|
||||
blank_line
|
||||
echo -e "| Common controllers that can be flashed that way are: |"
|
||||
echo -e "| - BigTreeTech SKR 1.3 / 1.4 (Turbo) / E3 / Mini E3 |"
|
||||
echo -e "| - Fysetc F6 / S6 (used with a Display + SD-Slot) |"
|
||||
echo -e "| - Fysetc Spider |"
|
||||
blank_line
|
||||
back_footer
|
||||
|
||||
local choice
|
||||
while true; do
|
||||
read -p "${cyan}###### Please select:${white} " choice
|
||||
case "${choice}" in
|
||||
B|b)
|
||||
clear && print_header
|
||||
init_flash_process
|
||||
break;;
|
||||
*)
|
||||
error_msg "Invalid command!";;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
function show_mcu_connection_help() {
|
||||
top_border
|
||||
echo -e "| ~~~~~~~~ < ? > Help: Flash MCU < ? > ~~~~~~~~ |"
|
||||
hr
|
||||
echo -e "| ${cyan}USB:${white} |"
|
||||
echo -e "| Selecting USB as the connection method will scan the |"
|
||||
echo -e "| USB ports for connected controller boards. This will |"
|
||||
echo -e "| be similar to the 'ls /dev/serial/by-id/*' command |"
|
||||
echo -e "| suggested by the official Klipper documentation for |"
|
||||
echo -e "| determining successfull USB connections! |"
|
||||
blank_line
|
||||
echo -e "| ${cyan}UART:${white} |"
|
||||
echo -e "| Selecting UART as the connection method will list all |"
|
||||
echo -e "| possible UART serial ports. Note: This method ALWAYS |"
|
||||
echo -e "| returns something as it seems impossible to determine |"
|
||||
echo -e "| if a valid Klipper controller board is connected or |"
|
||||
echo -e "| not. Because of that, you ${red}MUST${white} know which UART serial |"
|
||||
echo -e "| port your controller board is connected to when using |"
|
||||
echo -e "| this connection method. |"
|
||||
blank_line
|
||||
back_footer
|
||||
|
||||
local choice
|
||||
while true; do
|
||||
read -p "${cyan}###### Please select:${white} " choice
|
||||
case "${choice}" in
|
||||
B|b)
|
||||
clear && print_header
|
||||
select_mcu_connection
|
||||
break;;
|
||||
*)
|
||||
error_msg "Invalid command!";;
|
||||
esac
|
||||
done
|
||||
}
|
||||
@@ -1,334 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
show_flash_method_help(){
|
||||
top_border
|
||||
echo -e "| ~~~~~~~~ < ? > Help: Flash MCU < ? > ~~~~~~~~ |"
|
||||
hr
|
||||
echo -e "| ${cyan}Regular flashing method:${default} |"
|
||||
echo -e "| The default method to flash controller boards which |"
|
||||
echo -e "| are connected and updated over USB and not by placing |"
|
||||
echo -e "| a compiled firmware file onto an internal SD-Card. |"
|
||||
blank_line
|
||||
echo -e "| Common controllers that get flashed that way are: |"
|
||||
echo -e "| - Arduino Mega 2560 |"
|
||||
echo -e "| - Fysetc F6 / S6 (used without a Display + SD-Slot) |"
|
||||
blank_line
|
||||
echo -e "| ${cyan}Updating via SD-Card Update:${default} |"
|
||||
echo -e "| Many popular controller boards ship with a bootloader |"
|
||||
echo -e "| capable of updating the firmware via SD-Card. |"
|
||||
echo -e "| Choose this method if your controller board supports |"
|
||||
echo -e "| this way of updating. This method ONLY works for up- |"
|
||||
echo -e "| grading firmware. The initial flashing procedure must |"
|
||||
echo -e "| be done manually per the instructions that apply to |"
|
||||
echo -e "| your controller board. |"
|
||||
blank_line
|
||||
echo -e "| Common controllers that can be flashed that way are: |"
|
||||
echo -e "| - BigTreeTech SKR 1.3 / 1.4 (Turbo) / E3 / Mini E3 |"
|
||||
echo -e "| - Fysetc F6 / S6 (used with a Display + SD-Slot) |"
|
||||
echo -e "| - Fysetc Spider |"
|
||||
blank_line
|
||||
back_footer
|
||||
while true; do
|
||||
read -p "${cyan}###### Please select:${default} " choice
|
||||
case "$choice" in
|
||||
B|b)
|
||||
clear && print_header
|
||||
select_flash_method
|
||||
break;;
|
||||
*)
|
||||
print_unkown_cmd
|
||||
print_msg && clear_msg;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
select_flash_method(){
|
||||
top_border
|
||||
echo -e "| ~~~~~~~~~~~~ [ Flash MCU ] ~~~~~~~~~~~~ |"
|
||||
hr
|
||||
echo -e "| Please select the flashing method to flash your MCU. |"
|
||||
echo -e "| Make sure to only select a method your MCU supports. |"
|
||||
echo -e "| Not all MCUs support both methods! |"
|
||||
hr
|
||||
blank_line
|
||||
echo -e "| 1) Regular flashing method |"
|
||||
echo -e "| 2) Updating via SD-Card Update |"
|
||||
blank_line
|
||||
back_help_footer
|
||||
while true; do
|
||||
read -p "${cyan}###### Please select:${default} " choice
|
||||
case "$choice" in
|
||||
1)
|
||||
echo -e "###### > Regular flashing method"
|
||||
select_mcu_connection
|
||||
select_mcu_id
|
||||
[[ "$CONFIRM_FLASH" == true ]] && flash_mcu
|
||||
break;;
|
||||
2)
|
||||
echo -e "###### > SD-Card Update"
|
||||
select_mcu_connection
|
||||
select_mcu_id
|
||||
[[ "$CONFIRM_FLASH" == true ]] && flash_mcu_sd
|
||||
break;;
|
||||
B|b)
|
||||
advanced_menu
|
||||
break;;
|
||||
H|h)
|
||||
clear && print_header
|
||||
show_flash_method_help
|
||||
break;;
|
||||
*)
|
||||
print_unkown_cmd
|
||||
print_msg && clear_msg;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
select_mcu_id(){
|
||||
if [ ${#mcu_list[@]} -ge 1 ]; then
|
||||
top_border
|
||||
echo -e "| ${red}!!! ATTENTION !!!${default} |"
|
||||
hr
|
||||
echo -e "| Make sure, to select the correct MCU! |"
|
||||
echo -e "| ${red}ONLY flash a firmware created for the respective MCU!${default} |"
|
||||
bottom_border
|
||||
echo -e "${cyan}###### List of available MCU:${default}"
|
||||
### list all mcus
|
||||
id=0
|
||||
for mcu in ${mcu_list[@]}; do
|
||||
let id++
|
||||
echo -e " $id) $mcu"
|
||||
done
|
||||
### verify user input
|
||||
sel_index=""
|
||||
while [[ ! ($sel_index =~ ^[1-9]+$) ]] || [ "$sel_index" -gt "$id" ]; do
|
||||
echo
|
||||
read -p "${cyan}###### Select MCU to flash:${default} " sel_index
|
||||
if [[ ! ($sel_index =~ ^[1-9]+$) ]]; then
|
||||
warn_msg "Invalid input!"
|
||||
elif [ "$sel_index" -lt 1 ] || [ "$sel_index" -gt "$id" ]; then
|
||||
warn_msg "Please select a number between 1 and $id!"
|
||||
fi
|
||||
mcu_index=$(echo $((sel_index - 1)))
|
||||
selected_mcu_id="${mcu_list[$mcu_index]}"
|
||||
done
|
||||
### confirm selection
|
||||
while true; do
|
||||
echo -e "\n###### You selected:\n ● MCU #$sel_index: $selected_mcu_id\n"
|
||||
read -p "${cyan}###### Continue? (Y/n):${default} " yn
|
||||
case "$yn" in
|
||||
Y|y|Yes|yes|"")
|
||||
echo -e "###### > Yes"
|
||||
status_msg "Flashing $selected_mcu_id ..."
|
||||
CONFIRM_FLASH=true
|
||||
break;;
|
||||
N|n|No|no)
|
||||
echo -e "###### > No"
|
||||
CONFIRM_FLASH=false
|
||||
break;;
|
||||
*)
|
||||
print_unkown_cmd
|
||||
print_msg && clear_msg;;
|
||||
esac
|
||||
done
|
||||
fi
|
||||
}
|
||||
|
||||
flash_mcu(){
|
||||
do_action_service "stop" "klipper"
|
||||
make flash FLASH_DEVICE="${mcu_list[$mcu_index]}"
|
||||
### evaluate exit code of make flash
|
||||
if [ ! $? -eq 0 ]; then
|
||||
warn_msg "Flashing failed!"
|
||||
warn_msg "Please read the console output above!"
|
||||
else
|
||||
ok_msg "Flashing successfull!"
|
||||
fi
|
||||
do_action_service "start" "klipper"
|
||||
}
|
||||
|
||||
flash_mcu_sd(){
|
||||
flash_script="${HOME}/klipper/scripts/flash-sdcard.sh"
|
||||
|
||||
### write each supported board to the array to make it selectable
|
||||
board_list=()
|
||||
for board in $("$flash_script" -l | tail -n +2); do
|
||||
board_list+=($board)
|
||||
done
|
||||
|
||||
i=0
|
||||
top_border
|
||||
echo -e "| Please select the type of board that corresponds to |"
|
||||
echo -e "| the currently selected MCU ID you chose before. |"
|
||||
blank_line
|
||||
echo -e "| The following boards are currently supported: |"
|
||||
hr
|
||||
### display all supported boards to the user
|
||||
for board in ${board_list[@]}; do
|
||||
if [ $i -lt 10 ]; then
|
||||
printf "| $i) %-50s|\n" "${board_list[$i]}"
|
||||
else
|
||||
printf "| $i) %-49s|\n" "${board_list[$i]}"
|
||||
fi
|
||||
i=$((i + 1))
|
||||
done
|
||||
quit_footer
|
||||
|
||||
### make the user select one of the boards
|
||||
while true; do
|
||||
read -p "${cyan}###### Please select board type:${default} " choice
|
||||
if [ "$choice" = "q" ] || [ "$choice" = "Q" ]; then
|
||||
clear && advanced_menu && break
|
||||
elif [ "$choice" -le ${#board_list[@]} ]; then
|
||||
selected_board="${board_list[$choice]}"
|
||||
break
|
||||
else
|
||||
clear && print_header
|
||||
ERROR_MSG="Invalid choice!" && print_msg && clear_msg
|
||||
flash_mcu_sd
|
||||
fi
|
||||
done
|
||||
|
||||
while true; do
|
||||
top_border
|
||||
echo -e "| If your board is flashed with firmware that connects |"
|
||||
echo -e "| at a custom baud rate, please change it now. |"
|
||||
blank_line
|
||||
echo -e "| If you are unsure, stick to the default 250000! |"
|
||||
bottom_border
|
||||
echo -e "${cyan}###### Please set the baud rate:${default} "
|
||||
unset baud_rate
|
||||
while [[ ! $baud_rate =~ ^[0-9]+$ ]]; do
|
||||
read -e -i "250000" -e baud_rate
|
||||
selected_baud_rate=$baud_rate
|
||||
break
|
||||
done
|
||||
break
|
||||
done
|
||||
|
||||
###flash process
|
||||
do_action_service "stop" "klipper"
|
||||
"$flash_script" -b "$selected_baud_rate" "$selected_mcu_id" "$selected_board"
|
||||
### evaluate exit code of flash-sdcard.sh execution
|
||||
if [ ! $? -eq 0 ]; then
|
||||
warn_msg "Flashing failed!"
|
||||
warn_msg "Please read the console output above!"
|
||||
else
|
||||
ok_msg "Flashing successfull!"
|
||||
fi
|
||||
do_action_service "start" "klipper"
|
||||
}
|
||||
|
||||
build_fw(){
|
||||
if [ -d "$KLIPPER_DIR" ]; then
|
||||
cd "$KLIPPER_DIR"
|
||||
status_msg "Initializing firmware build ..."
|
||||
dep=(build-essential dpkg-dev make)
|
||||
dependency_check
|
||||
make clean && make menuconfig
|
||||
status_msg "Building firmware ..."
|
||||
make && ok_msg "Firmware built!"
|
||||
else
|
||||
ERROR_MSG="Klipper was not found!\n Can not build firmware without Klipper!"
|
||||
print_msg && clear_msg && return 1
|
||||
fi
|
||||
}
|
||||
|
||||
select_mcu_connection(){
|
||||
echo
|
||||
top_border
|
||||
echo -e "| ${yellow}Make sure to have the controller board connected now!${default} |"
|
||||
blank_line
|
||||
echo -e "| How is the controller board connected to the host? |"
|
||||
echo -e "| 1) USB |"
|
||||
echo -e "| 2) UART |"
|
||||
bottom_border
|
||||
while true; do
|
||||
read -p "${cyan}###### Connection method:${default} " choice
|
||||
case "$choice" in
|
||||
1)
|
||||
retrieve_id "USB"
|
||||
break;;
|
||||
2)
|
||||
retrieve_id "UART"
|
||||
break;;
|
||||
*)
|
||||
print_unkown_cmd
|
||||
print_msg && clear_msg;;
|
||||
esac
|
||||
done
|
||||
unset mcu_count
|
||||
|
||||
if [[ "${#mcu_list[@]}" -lt 1 ]]; then
|
||||
warn_msg "No MCU found!"
|
||||
warn_msg "MCU not plugged in or not detectable!"
|
||||
echo
|
||||
fi
|
||||
}
|
||||
|
||||
retrieve_id(){
|
||||
status_msg "Identifying MCU ..."
|
||||
sleep 1
|
||||
mcu_list=()
|
||||
mcu_count=1
|
||||
[ "$1" = "USB" ] && path="/dev/serial/by-id/*"
|
||||
[ "$1" = "UART" ] && path="/dev/ttyAMA0"
|
||||
if [[ "$(ls $path)" != "" ]] ; then
|
||||
for mcu in $path; do
|
||||
declare "mcu_id_$mcu_count"="$mcu"
|
||||
mcu_id="mcu_id_$mcu_count"
|
||||
mcu_list+=("${!mcu_id}")
|
||||
echo -e " ● ($1) MCU #$mcu_count: ${cyan}$mcu${default}\n"
|
||||
let mcu_count++
|
||||
done
|
||||
fi 2>/dev/null
|
||||
}
|
||||
|
||||
check_usergroup_dialout(){
|
||||
if grep -q "dialout" </etc/group && ! grep -q "dialout" <(groups "${USER}"); then
|
||||
group_dialout=false
|
||||
else
|
||||
group_dialout=true
|
||||
fi
|
||||
if grep -q "tty" </etc/group && ! grep -q "tty" <(groups "${USER}"); then
|
||||
group_tty=false
|
||||
else
|
||||
group_tty=true
|
||||
fi
|
||||
if [ "$group_dialout" == "false" ] || [ "$group_tty" == "false" ] ; then
|
||||
top_border
|
||||
echo -e "| ${yellow}WARNING: Your current user is not in group:${default} |"
|
||||
[ "$group_tty" == "false" ] && echo -e "| ${yellow}● tty${default} |"
|
||||
[ "$group_dialout" == "false" ] && echo -e "| ${yellow}● dialout${default} |"
|
||||
blank_line
|
||||
echo -e "| It is possible that you won't be able to successfully |"
|
||||
echo -e "| flash without your user being a member of that group. |"
|
||||
echo -e "| If you want to add the current user to the group(s) |"
|
||||
echo -e "| listed above, answer with 'Y'. Else skip with 'n'. |"
|
||||
blank_line
|
||||
echo -e "| ${yellow}INFO:${default} |"
|
||||
echo -e "| ${yellow}Relog required for group assignments to take effect!${default} |"
|
||||
bottom_border
|
||||
while true; do
|
||||
read -p "${cyan}###### Add user '${USER}' to group(s) now? (Y/n):${default} " yn
|
||||
case "$yn" in
|
||||
Y|y|Yes|yes|"")
|
||||
echo -e "###### > Yes"
|
||||
status_msg "Adding user '${USER}' to group(s) ..."
|
||||
if [ "$group_tty" == "false" ]; then
|
||||
sudo usermod -a -G tty "${USER}" && ok_msg "Group 'tty' assigned!"
|
||||
fi
|
||||
if [ "$group_dialout" == "false" ]; then
|
||||
sudo usermod -a -G dialout "${USER}" && ok_msg "Group 'dialout' assigned!"
|
||||
fi
|
||||
ok_msg "You need to relog/restart for the group(s) to be applied!" && exit 0;;
|
||||
N|n|No|no)
|
||||
echo -e "###### > No"
|
||||
break;;
|
||||
*)
|
||||
print_unkown_cmd
|
||||
print_msg && clear_msg;;
|
||||
esac
|
||||
done
|
||||
fi
|
||||
}
|
||||
510
scripts/fluidd.sh
Normal file
510
scripts/fluidd.sh
Normal file
@@ -0,0 +1,510 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
#=======================================================================#
|
||||
# Copyright (C) 2020 - 2023 Dominik Willner <th33xitus@gmail.com> #
|
||||
# #
|
||||
# This file is part of KIAUH - Klipper Installation And Update Helper #
|
||||
# https://github.com/dw-0/kiauh #
|
||||
# #
|
||||
# This file may be distributed under the terms of the GNU GPLv3 license #
|
||||
#=======================================================================#
|
||||
|
||||
set -e
|
||||
|
||||
#===================================================#
|
||||
#================== INSTALL FLUIDD =================#
|
||||
#===================================================#
|
||||
|
||||
function install_fluidd() {
|
||||
if [[ -z $(moonraker_systemd) ]]; then
|
||||
local error="Moonraker not installed! It's recommended to install Moonraker first!"
|
||||
print_error "${error}"
|
||||
while true; do
|
||||
local yn
|
||||
read -p "${cyan}###### Proceed to install Fluidd without installing Moonraker? (y/N):${white} " yn
|
||||
case "${yn}" in
|
||||
Y|y|Yes|yes)
|
||||
select_msg "Yes"
|
||||
break;;
|
||||
N|n|No|no|"")
|
||||
select_msg "No"
|
||||
abort_msg "Exiting Fluidd setup ...\n"
|
||||
return;;
|
||||
*)
|
||||
error_msg "Invalid Input!";;
|
||||
esac
|
||||
done
|
||||
fi
|
||||
|
||||
### checking dependencies
|
||||
local dep=(wget nginx)
|
||||
dependency_check "${dep[@]}"
|
||||
### detect conflicting Haproxy and Apache2 installations
|
||||
detect_conflicting_packages
|
||||
|
||||
status_msg "Initializing Fluidd installation ..."
|
||||
### first, we create a backup of the full klipper_config dir - safety first!
|
||||
backup_config_dir
|
||||
|
||||
### check for other enabled web interfaces
|
||||
unset SET_LISTEN_PORT
|
||||
detect_enabled_sites
|
||||
|
||||
### check if another site already listens to port 80
|
||||
fluidd_port_check
|
||||
|
||||
### download fluidd
|
||||
download_fluidd
|
||||
|
||||
### ask user to install the recommended webinterface macros
|
||||
install_fluidd_macros
|
||||
|
||||
### create /etc/nginx/conf.d/upstreams.conf
|
||||
set_upstream_nginx_cfg
|
||||
### create /etc/nginx/sites-available/<interface config>
|
||||
set_nginx_cfg "fluidd"
|
||||
### nginx on ubuntu 21 and above needs special permissions to access the files
|
||||
set_nginx_permissions
|
||||
|
||||
### symlink nginx log
|
||||
symlink_webui_nginx_log "fluidd"
|
||||
|
||||
### add fluidd to the update manager in moonraker.conf
|
||||
patch_fluidd_update_manager
|
||||
|
||||
fetch_webui_ports #WIP
|
||||
|
||||
### confirm message
|
||||
print_confirm "Fluidd has been set up!"
|
||||
}
|
||||
|
||||
function install_fluidd_macros() {
|
||||
local yn
|
||||
while true; do
|
||||
echo
|
||||
top_border
|
||||
echo -e "| It is recommended to use special macros in order to |"
|
||||
echo -e "| have Fluidd fully functional and working. |"
|
||||
blank_line
|
||||
echo -e "| The recommended macros for Fluidd can be found here: |"
|
||||
echo -e "| https://github.com/fluidd-core/fluidd-config |"
|
||||
blank_line
|
||||
echo -e "| If you already use these macros skip this step. |"
|
||||
echo -e "| Otherwise you should consider to answer with 'yes' to |"
|
||||
echo -e "| download the recommended macros. |"
|
||||
bottom_border
|
||||
read -p "${cyan}###### Download the recommended macros? (Y/n):${white} " yn
|
||||
case "${yn}" in
|
||||
Y|y|Yes|yes|"")
|
||||
select_msg "Yes"
|
||||
download_fluidd_macros
|
||||
break;;
|
||||
N|n|No|no)
|
||||
select_msg "No"
|
||||
break;;
|
||||
*)
|
||||
print_error "Invalid command!";;
|
||||
esac
|
||||
done
|
||||
return
|
||||
}
|
||||
|
||||
function download_fluidd_macros() {
|
||||
local ms_cfg_repo path configs regex line gcode_dir
|
||||
|
||||
ms_cfg_repo="https://github.com/fluidd-core/fluidd-config.git"
|
||||
regex="${HOME//\//\\/}\/([A-Za-z0-9_]+)\/config\/printer\.cfg"
|
||||
configs=$(find "${HOME}" -maxdepth 3 -regextype posix-extended -regex "${regex}" | sort)
|
||||
|
||||
if [[ -z ${configs} ]]; then
|
||||
print_error "No printer.cfg found! Installation of Macros will be skipped ..."
|
||||
log_error "execution stopped! reason: no printer.cfg found"
|
||||
return
|
||||
fi
|
||||
|
||||
status_msg "Cloning fluidd-config ..."
|
||||
[[ -d "${HOME}/fluidd-config" ]] && rm -rf "${HOME}/fluidd-config"
|
||||
if git clone --recurse-submodules "${ms_cfg_repo}" "${HOME}/fluidd-config"; then
|
||||
for config in ${configs}; do
|
||||
path=$(echo "${config}" | rev | cut -d"/" -f2- | rev)
|
||||
|
||||
if [[ -e "${path}/fluidd.cfg" && ! -h "${path}/fluidd.cfg" ]]; then
|
||||
warn_msg "Attention! Existing fluidd.cfg detected!"
|
||||
warn_msg "The file will be renamed to 'fluidd.bak.cfg' to be able to continue with the installation."
|
||||
if ! mv "${path}/fluidd.cfg" "${path}/fluidd.bak.cfg"; then
|
||||
error_msg "Renaming fluidd.cfg failed! Aborting installation ..."
|
||||
return
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ -h "${path}/fluidd.cfg" ]]; then
|
||||
warn_msg "Recreating symlink in ${path} ..."
|
||||
rm -rf "${path}/fluidd.cfg"
|
||||
fi
|
||||
|
||||
if ! ln -sf "${HOME}/fluidd-config/client.cfg" "${path}/fluidd.cfg"; then
|
||||
error_msg "Creating symlink failed! Aborting installation ..."
|
||||
return
|
||||
fi
|
||||
|
||||
if ! grep -Eq "^\[include fluidd.cfg\]$" "${path}/printer.cfg"; then
|
||||
log_info "${path}/printer.cfg"
|
||||
sed -i "1 i [include fluidd.cfg]" "${path}/printer.cfg"
|
||||
fi
|
||||
|
||||
line=$(($(grep -n "\[include fluidd.cfg\]" "${path}/printer.cfg" | tail -1 | cut -d: -f1) + 1))
|
||||
gcode_dir=${path/config/gcodes}
|
||||
if ! grep -Eq "^\[virtual_sdcard\]$" "${path}/printer.cfg"; then
|
||||
log_info "${path}/printer.cfg"
|
||||
sed -i "${line} i \[virtual_sdcard]\npath: ${gcode_dir}\non_error_gcode: CANCEL_PRINT\n" "${path}/printer.cfg"
|
||||
fi
|
||||
done
|
||||
else
|
||||
print_error "Cloning failed! Aborting installation ..."
|
||||
log_error "execution stopped! reason: cloning failed"
|
||||
return
|
||||
fi
|
||||
|
||||
patch_fluidd_config_update_manager
|
||||
|
||||
ok_msg "Done!"
|
||||
}
|
||||
|
||||
function download_fluidd() {
|
||||
local url
|
||||
url=$(get_fluidd_download_url)
|
||||
|
||||
status_msg "Downloading Fluidd from ${url} ..."
|
||||
|
||||
if [[ -d ${FLUIDD_DIR} ]]; then
|
||||
rm -rf "${FLUIDD_DIR}"
|
||||
fi
|
||||
|
||||
mkdir "${FLUIDD_DIR}" && cd "${FLUIDD_DIR}"
|
||||
|
||||
if wget "${url}"; then
|
||||
ok_msg "Download complete!"
|
||||
status_msg "Extracting archive ..."
|
||||
unzip -q -o ./*.zip && ok_msg "Done!"
|
||||
status_msg "Remove downloaded archive ..."
|
||||
rm -rf ./*.zip && ok_msg "Done!"
|
||||
else
|
||||
print_error "Downloading Fluidd from\n ${url}\n failed!"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
#===================================================#
|
||||
#================== REMOVE FLUIDD ==================#
|
||||
#===================================================#
|
||||
|
||||
function remove_fluidd_dir() {
|
||||
[[ ! -d ${FLUIDD_DIR} ]] && return
|
||||
|
||||
status_msg "Removing Fluidd directory ..."
|
||||
rm -rf "${FLUIDD_DIR}" && ok_msg "Directory removed!"
|
||||
}
|
||||
|
||||
function remove_fluidd_nginx_config() {
|
||||
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
|
||||
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
|
||||
}
|
||||
|
||||
function remove_fluidd_logs() {
|
||||
local files
|
||||
files=$(find /var/log/nginx -name "fluidd*" 2> /dev/null | sort)
|
||||
|
||||
if [[ -n ${files} ]]; then
|
||||
for file in ${files}; do
|
||||
status_msg "Removing ${file} ..."
|
||||
sudo rm -f "${file}"
|
||||
ok_msg "${file} removed!"
|
||||
done
|
||||
fi
|
||||
}
|
||||
|
||||
function remove_fluidd_log_symlinks() {
|
||||
local files regex
|
||||
|
||||
regex="${HOME//\//\\/}\/([A-Za-z0-9_]+)\/logs\/fluidd-.*"
|
||||
files=$(find "${HOME}" -maxdepth 3 -regextype posix-extended -regex "${regex}" 2> /dev/null | sort)
|
||||
|
||||
if [[ -n ${files} ]]; then
|
||||
for file in ${files}; do
|
||||
status_msg "Removing ${file} ..."
|
||||
rm -f "${file}"
|
||||
ok_msg "${file} removed!"
|
||||
done
|
||||
fi
|
||||
}
|
||||
|
||||
function remove_legacy_fluidd_log_symlinks() {
|
||||
local files
|
||||
files=$(find "${HOME}/klipper_logs" -name "fluidd*" 2> /dev/null | sort)
|
||||
|
||||
if [[ -n ${files} ]]; then
|
||||
for file in ${files}; do
|
||||
status_msg "Removing ${file} ..."
|
||||
rm -f "${file}"
|
||||
ok_msg "${file} removed!"
|
||||
done
|
||||
fi
|
||||
}
|
||||
|
||||
function remove_fluidd_config() {
|
||||
if [[ -d "${HOME}/fluidd-config" ]]; then
|
||||
status_msg "Removing ${HOME}/fluidd-config ..."
|
||||
rm -rf "${HOME}/fluidd-config"
|
||||
ok_msg "${HOME}/fluidd-config removed!"
|
||||
print_confirm "Fluidd-Config successfully removed!"
|
||||
fi
|
||||
}
|
||||
|
||||
function remove_fluidd() {
|
||||
remove_fluidd_dir
|
||||
remove_fluidd_nginx_config
|
||||
remove_fluidd_logs
|
||||
remove_fluidd_log_symlinks
|
||||
remove_legacy_fluidd_log_symlinks
|
||||
|
||||
### remove fluidd_port from ~/.kiauh.ini
|
||||
sed -i "/^fluidd_port=/d" "${INI_FILE}"
|
||||
|
||||
print_confirm "Fluidd successfully removed!"
|
||||
}
|
||||
|
||||
#===================================================#
|
||||
#================== UPDATE FLUIDD ==================#
|
||||
#===================================================#
|
||||
|
||||
function update_fluidd() {
|
||||
backup_before_update "fluidd"
|
||||
status_msg "Updating Fluidd ..."
|
||||
download_fluidd
|
||||
match_nginx_configs
|
||||
symlink_webui_nginx_log "fluidd"
|
||||
print_confirm "Fluidd successfully updated!"
|
||||
}
|
||||
|
||||
#===================================================#
|
||||
#================== FLUIDD STATUS ==================#
|
||||
#===================================================#
|
||||
|
||||
function get_fluidd_status() {
|
||||
local status
|
||||
local data_arr=("${FLUIDD_DIR}" "${NGINX_SA}/fluidd" "${NGINX_SE}/fluidd")
|
||||
|
||||
### 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="Installed!"
|
||||
elif (( filecount == 0 )); then
|
||||
status="Not installed!"
|
||||
else
|
||||
status="Incomplete!"
|
||||
fi
|
||||
echo "${status}"
|
||||
}
|
||||
|
||||
function get_local_fluidd_version() {
|
||||
local versionfile="${FLUIDD_DIR}/.version"
|
||||
local relinfofile="${FLUIDD_DIR}/release_info.json"
|
||||
local version
|
||||
|
||||
if [[ -f ${relinfofile} ]]; then
|
||||
version=$(grep -o '"version":"[^"]*' "${relinfofile}" | grep -o '[^"]*$')
|
||||
elif [[ -f ${versionfile} ]]; then
|
||||
version=$(head -n 1 "${versionfile}")
|
||||
fi
|
||||
|
||||
echo "${version}"
|
||||
}
|
||||
|
||||
function get_remote_fluidd_version() {
|
||||
[[ ! $(dpkg-query -f'${Status}' --show curl 2>/dev/null) = *\ installed ]] && return
|
||||
|
||||
local tags
|
||||
tags=$(curl -s "https://api.github.com/repos/fluidd-core/fluidd/tags" | grep "name" | cut -d'"' -f4)
|
||||
echo "${tags}" | head -1
|
||||
}
|
||||
|
||||
function compare_fluidd_versions() {
|
||||
local versions local_ver remote_ver
|
||||
local_ver="$(get_local_fluidd_version)"
|
||||
remote_ver="$(get_remote_fluidd_version)"
|
||||
|
||||
if [[ ${local_ver} != "${remote_ver}" && ${local_ver} != "" ]]; then
|
||||
versions="${yellow}$(printf " %-14s" "${local_ver}")${white}"
|
||||
versions+="|${green}$(printf " %-13s" "${remote_ver}")${white}"
|
||||
# add moonraker to application_updates_available in kiauh.ini
|
||||
add_to_application_updates "fluidd"
|
||||
else
|
||||
versions="${green}$(printf " %-14s" "${local_ver}")${white}"
|
||||
versions+="|${green}$(printf " %-13s" "${remote_ver}")${white}"
|
||||
fi
|
||||
|
||||
echo "${versions}"
|
||||
}
|
||||
|
||||
#================================================#
|
||||
#=================== HELPERS ====================#
|
||||
#================================================#
|
||||
|
||||
function get_fluidd_download_url() {
|
||||
local releases_by_tag tags tag unstable_url url
|
||||
|
||||
### latest stable download url
|
||||
url="https://github.com/fluidd-core/fluidd/releases/latest/download/fluidd.zip"
|
||||
|
||||
read_kiauh_ini "${FUNCNAME[0]}"
|
||||
if [[ ${fluidd_install_unstable} == "true" ]]; then
|
||||
releases_by_tag="https://api.github.com/repos/fluidd-core/fluidd/tags"
|
||||
tags=$(curl -s "${releases_by_tag}" | grep "name" | cut -d'"' -f4)
|
||||
tag=$(echo "${tags}" | head -1)
|
||||
|
||||
### latest unstable download url including pre-releases (alpha, beta, rc)
|
||||
unstable_url="https://github.com/fluidd-core/fluidd/releases/download/${tag}/fluidd.zip"
|
||||
|
||||
if [[ ${unstable_url} == *"download//"* ]]; then
|
||||
warn_msg "Download URL broken! Falling back to URL of latest stable release!"
|
||||
else
|
||||
url=${unstable_url}
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "${url}"
|
||||
}
|
||||
|
||||
function fluidd_port_check() {
|
||||
if [[ ${FLUIDD_ENABLED} == "false" ]]; then
|
||||
|
||||
if [[ ${SITE_ENABLED} == "true" ]]; then
|
||||
status_msg "Detected other enabled interfaces:"
|
||||
|
||||
[[ ${MAINSAIL_ENABLED} == "true" ]] && \
|
||||
echo " ${cyan}● Mainsail - Port: ${MAINSAIL_PORT}${white}"
|
||||
|
||||
if [[ ${MAINSAIL_PORT} == "80" ]]; then
|
||||
PORT_80_BLOCKED="true"
|
||||
select_fluidd_port
|
||||
fi
|
||||
else
|
||||
DEFAULT_PORT=$(grep listen "${KIAUH_SRCDIR}/resources/fluidd" | 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
|
||||
}
|
||||
|
||||
function select_fluidd_port() {
|
||||
if [[ ${PORT_80_BLOCKED} == "true" ]]; then
|
||||
echo
|
||||
top_border
|
||||
echo -e "| ${red}!!!WARNING!!!${white} |"
|
||||
echo -e "| ${red}You need to choose a different port for Fluidd!${white} |"
|
||||
echo -e "| ${red}The following web interface is listening at port 80:${white} |"
|
||||
blank_line
|
||||
[[ ${MAINSAIL_PORT} == "80" ]] && echo "| ● Mainsail |"
|
||||
blank_line
|
||||
echo -e "| Make sure you don't choose a port which was already |"
|
||||
echo -e "| assigned to another webinterface! |"
|
||||
blank_line
|
||||
echo -e "| Be aware: there is ${red}NO${white} sanity check for the following |"
|
||||
echo -e "| input. So make sure to choose a valid port! |"
|
||||
bottom_border
|
||||
|
||||
local new_port re="^[0-9]+$"
|
||||
while true; do
|
||||
read -p "${cyan}Please enter a new Port:${white} " new_port
|
||||
if [[ ${new_port} =~ ${re} && ${new_port} != "${MAINSAIL_PORT}" ]]; then
|
||||
select_msg "Setting port ${new_port} for Fluidd!"
|
||||
SET_LISTEN_PORT=${new_port}
|
||||
break
|
||||
else
|
||||
if [[ ! ${new_port} =~ ${re} ]]; then
|
||||
error_msg "Invalid input!"
|
||||
else
|
||||
error_msg "Port already taken! Select a different one!"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
fi
|
||||
}
|
||||
|
||||
function patch_fluidd_update_manager() {
|
||||
local patched moonraker_configs regex
|
||||
regex="${HOME//\//\\/}\/([A-Za-z0-9_]+)\/config\/moonraker\.conf"
|
||||
moonraker_configs=$(find "${HOME}" -maxdepth 3 -type f -regextype posix-extended -regex "${regex}" | sort)
|
||||
|
||||
patched="false"
|
||||
for conf in ${moonraker_configs}; do
|
||||
if ! grep -Eq "^\[update_manager fluidd\]\s*$" "${conf}"; then
|
||||
### add new line to conf if it doesn't end with one
|
||||
[[ $(tail -c1 "${conf}" | wc -l) -eq 0 ]] && echo "" >> "${conf}"
|
||||
|
||||
### add Fluidds update manager section to moonraker.conf
|
||||
status_msg "Adding Fluidd to update manager in file:\n ${conf}"
|
||||
/bin/sh -c "cat >> ${conf}" << MOONRAKER_CONF
|
||||
|
||||
[update_manager fluidd]
|
||||
type: web
|
||||
channel: stable
|
||||
repo: fluidd-core/fluidd
|
||||
path: ~/fluidd
|
||||
MOONRAKER_CONF
|
||||
|
||||
fi
|
||||
|
||||
patched="true"
|
||||
done
|
||||
|
||||
if [[ ${patched} == "true" ]]; then
|
||||
do_action_service "restart" "moonraker"
|
||||
fi
|
||||
}
|
||||
|
||||
function patch_fluidd_config_update_manager() {
|
||||
local patched moonraker_configs regex
|
||||
regex="${HOME//\//\\/}\/([A-Za-z0-9_]+)\/config\/moonraker\.conf"
|
||||
moonraker_configs=$(find "${HOME}" -maxdepth 3 -type f -regextype posix-extended -regex "${regex}" | sort)
|
||||
|
||||
patched="false"
|
||||
for conf in ${moonraker_configs}; do
|
||||
if ! grep -Eq "^\[update_manager fluidd-config\]\s*$" "${conf}"; then
|
||||
### add new line to conf if it doesn't end with one
|
||||
[[ $(tail -c1 "${conf}" | wc -l) -eq 0 ]] && echo "" >> "${conf}"
|
||||
|
||||
### add Fluidds update manager section to moonraker.conf
|
||||
status_msg "Adding Fluidd-Config to update manager in file:\n ${conf}"
|
||||
/bin/sh -c "cat >> ${conf}" << MOONRAKER_CONF
|
||||
|
||||
[update_manager fluidd-config]
|
||||
type: git_repo
|
||||
primary_branch: master
|
||||
path: ~/fluidd-config
|
||||
origin: https://github.com/fluidd-core/fluidd-config.git
|
||||
managed_services: klipper
|
||||
MOONRAKER_CONF
|
||||
|
||||
fi
|
||||
|
||||
patched="true"
|
||||
done
|
||||
|
||||
if [[ ${patched} == "true" ]]; then
|
||||
do_action_service "restart" "moonraker"
|
||||
fi
|
||||
}
|
||||
@@ -1,419 +0,0 @@
|
||||
### base variables
|
||||
SYSTEMDDIR="/etc/systemd/system"
|
||||
|
||||
# setting up some frequently used functions
|
||||
check_euid(){
|
||||
if [ "$EUID" -eq 0 ]
|
||||
then
|
||||
echo -e "${red}"
|
||||
top_border
|
||||
echo -e "| !!! THIS SCRIPT MUST NOT RAN AS ROOT !!! |"
|
||||
bottom_border
|
||||
echo -e "${default}"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
check_klipper_cfg_path(){
|
||||
source_kiauh_ini
|
||||
if [ -z $klipper_cfg_loc ]; then
|
||||
echo
|
||||
top_border
|
||||
echo -e "| ${red}!!! WARNING !!!${default} |"
|
||||
echo -e "| ${red}No Klipper configuration directory set!${default} |"
|
||||
hr
|
||||
echo -e "| Before we can continue, KIAUH needs to know where |"
|
||||
echo -e "| you want your printer configuration to be. |"
|
||||
blank_line
|
||||
echo -e "| Please specify a folder where your Klipper configu- |"
|
||||
echo -e "| ration is stored or, if you don't have one yet, in |"
|
||||
echo -e "| which it should be saved after the installation. |"
|
||||
bottom_border
|
||||
change_klipper_cfg_path
|
||||
fi
|
||||
}
|
||||
|
||||
change_klipper_cfg_path(){
|
||||
source_kiauh_ini
|
||||
old_klipper_cfg_loc="$klipper_cfg_loc"
|
||||
EXAMPLE_FOLDER=$(printf "%s/your_config_folder" "${HOME}")
|
||||
while true; do
|
||||
top_border
|
||||
echo -e "| ${red}IMPORTANT:${default} |"
|
||||
echo -e "| Please enter the new path in the following format: |"
|
||||
printf "| ${yellow}%-51s${default} |\n" "$EXAMPLE_FOLDER"
|
||||
blank_line
|
||||
echo -e "| By default 'klipper_config' is recommended! |"
|
||||
bottom_border
|
||||
echo
|
||||
echo -e "${cyan}###### Please set the Klipper config directory:${default} "
|
||||
if [ -z "$old_klipper_cfg_loc" ]; then
|
||||
read -e -i "/home/${USER}/klipper_config" -e new_klipper_cfg_loc
|
||||
else
|
||||
read -e -i "$old_klipper_cfg_loc" -e new_klipper_cfg_loc
|
||||
fi
|
||||
echo
|
||||
read -p "${cyan}###### Set config directory to '${yellow}$new_klipper_cfg_loc${cyan}' ? (Y/n):${default} " yn
|
||||
case "$yn" in
|
||||
Y|y|Yes|yes|"")
|
||||
echo -e "###### > Yes"
|
||||
|
||||
### backup the old config dir
|
||||
backup_klipper_config_dir
|
||||
|
||||
### write new location to kiauh.ini
|
||||
sed -i "s|klipper_cfg_loc=$old_klipper_cfg_loc|klipper_cfg_loc=$new_klipper_cfg_loc|" $INI_FILE
|
||||
status_msg "Directory set to '$new_klipper_cfg_loc'!"
|
||||
|
||||
### write new location to klipper and moonraker service
|
||||
set_klipper_cfg_path
|
||||
echo; ok_msg "Config directory changed!"
|
||||
break;;
|
||||
N|n|No|no)
|
||||
echo -e "###### > No"
|
||||
change_klipper_cfg_path
|
||||
break;;
|
||||
*)
|
||||
print_unkown_cmd
|
||||
print_msg && clear_msg;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
set_klipper_cfg_path(){
|
||||
### stop services
|
||||
do_action_service "stop" "klipper"
|
||||
do_action_service "stop" "moonraker"
|
||||
|
||||
### copy config files to new klipper config folder
|
||||
if [ ! -z "$old_klipper_cfg_loc" ] && [ -d "$old_klipper_cfg_loc" ]; then
|
||||
if [ ! -d "$new_klipper_cfg_loc" ]; then
|
||||
status_msg "Copy config files to '$new_klipper_cfg_loc' ..."
|
||||
mkdir -p "$new_klipper_cfg_loc"
|
||||
cd $old_klipper_cfg_loc
|
||||
cp -r -v ./* "$new_klipper_cfg_loc"
|
||||
ok_msg "Done!"
|
||||
fi
|
||||
fi
|
||||
|
||||
SERVICE_FILES=$(find "$SYSTEMDDIR" -regextype posix-extended -regex "$SYSTEMDDIR/klipper(-[^0])+[0-9]*.service")
|
||||
### handle single klipper instance service file
|
||||
if [ -f $SYSTEMDDIR/klipper.service ]; then
|
||||
status_msg "Configuring Klipper for new path ..."
|
||||
sudo sed -i -r "/ExecStart=/ s|klippy.py (.+)\/printer.cfg|klippy.py $new_klipper_cfg_loc/printer.cfg|" $SYSTEMDDIR/klipper.service
|
||||
ok_msg "OK!"
|
||||
elif [ -n "$SERVICE_FILES" ]; then
|
||||
### handle multi klipper instance service file
|
||||
status_msg "Configuring Klipper for new path ..."
|
||||
for service in $SERVICE_FILES; do
|
||||
sudo sed -i -r "/ExecStart=/ s|klippy.py (.+)\/printer_|klippy.py $new_klipper_cfg_loc/printer_|" "$service"
|
||||
done
|
||||
ok_msg "OK!"
|
||||
fi
|
||||
|
||||
SERVICE_FILES=$(find "$SYSTEMDDIR" -regextype posix-extended -regex "$SYSTEMDDIR/moonraker(-[^0])+[0-9]*.service")
|
||||
### handle single moonraker instance service and moonraker.conf file
|
||||
if [ -f $SYSTEMDDIR/moonraker.service ]; then
|
||||
status_msg "Configuring Moonraker for new path ..."
|
||||
sudo sed -i -r "/ExecStart=/ s|-c (.+)\/moonraker\.conf|-c $new_klipper_cfg_loc/moonraker.conf|" $SYSTEMDDIR/moonraker.service
|
||||
|
||||
### replace old file path with new one in moonraker.conf
|
||||
sed -i -r "/config_path:/ s|config_path:.*|config_path: $new_klipper_cfg_loc|" $new_klipper_cfg_loc/moonraker.conf
|
||||
ok_msg "OK!"
|
||||
elif [ -n "$SERVICE_FILES" ]; then
|
||||
### handle multi moonraker instance service file
|
||||
status_msg "Configuring Moonraker for new path ..."
|
||||
for service in $SERVICE_FILES; do
|
||||
sudo sed -i -r "/ExecStart=/ s|-c (.+)\/printer_|-c $new_klipper_cfg_loc/printer_|" "$service"
|
||||
done
|
||||
MR_CONFS=$(find "$new_klipper_cfg_loc" -regextype posix-extended -regex "$new_klipper_cfg_loc/printer_[1-9]+/moonraker.conf")
|
||||
### replace old file path with new one in moonraker.conf
|
||||
for moonraker_conf in $MR_CONFS; do
|
||||
loc=$(echo "$moonraker_conf" | rev | cut -d"/" -f2- | rev)
|
||||
sed -i -r "/config_path:/ s|config_path:.*|config_path: $loc|" "$moonraker_conf"
|
||||
done
|
||||
ok_msg "OK!"
|
||||
fi
|
||||
|
||||
### reloading units
|
||||
sudo systemctl daemon-reload
|
||||
|
||||
### restart services
|
||||
do_action_service "restart" "klipper"
|
||||
do_action_service "restart" "moonraker"
|
||||
}
|
||||
|
||||
source_kiauh_ini(){
|
||||
source $INI_FILE
|
||||
}
|
||||
|
||||
do_action_service(){
|
||||
shopt -s extglob # enable extended globbing
|
||||
SERVICES="$SYSTEMDDIR/$2?(-*([0-9])).service"
|
||||
### set a variable for the ok and status messages
|
||||
[ "$1" == "start" ] && ACTION1="started" && ACTION2="Starting"
|
||||
[ "$1" == "stop" ] && ACTION1="stopped" && ACTION2="Stopping"
|
||||
[ "$1" == "restart" ] && ACTION1="restarted" && ACTION2="Restarting"
|
||||
[ "$1" == "enable" ] && ACTION1="enabled" && ACTION2="Enabling"
|
||||
[ "$1" == "disable" ] && ACTION1="disabled" && ACTION2="Disabling"
|
||||
|
||||
if ls $SERVICES 2>/dev/null 1>&2; then
|
||||
for service in $(ls $SERVICES | rev | cut -d"/" -f1 | rev); do
|
||||
status_msg "$ACTION2 $service ..."
|
||||
sudo systemctl $1 "$service"
|
||||
ok_msg "$service $ACTION1!"
|
||||
done
|
||||
fi
|
||||
shopt -u extglob # disable extended globbing
|
||||
}
|
||||
|
||||
toggle_octoprint_service(){
|
||||
if systemctl list-unit-files | grep -E "octoprint.*" | grep "enabled" &>/dev/null; then
|
||||
do_action_service "stop" "octoprint"
|
||||
do_action_service "disable" "octoprint"
|
||||
sleep 2
|
||||
CONFIRM_MSG=" OctoPrint Service is now >>> DISABLED <<< !"
|
||||
elif systemctl list-unit-files | grep -E "octoprint.*" | grep "disabled" &>/dev/null; then
|
||||
do_action_service "enable" "octoprint"
|
||||
do_action_service "start" "octoprint"
|
||||
sleep 2
|
||||
CONFIRM_MSG=" OctoPrint Service is now >>> ENABLED <<< !"
|
||||
else
|
||||
ERROR_MSG=" You cannot activate a service that does not exist!"
|
||||
fi
|
||||
}
|
||||
|
||||
read_octoprint_service_status(){
|
||||
unset OPRINT_SERVICE_STATUS
|
||||
if [ ! -f "/etc/systemd/system/octoprint.service" ]; then
|
||||
return 0
|
||||
fi
|
||||
if systemctl list-unit-files | grep -E "octoprint*" | grep "enabled" &>/dev/null; then
|
||||
OPRINT_SERVICE_STATUS="${red}[Disable]${default} OctoPrint Service "
|
||||
else
|
||||
OPRINT_SERVICE_STATUS="${green}[Enable]${default} OctoPrint Service "
|
||||
fi
|
||||
}
|
||||
|
||||
start_klipperscreen(){
|
||||
status_msg "Starting KlipperScreen Service ..."
|
||||
sudo systemctl start KlipperScreen && ok_msg "KlipperScreen Service started!"
|
||||
}
|
||||
|
||||
stop_klipperscreen(){
|
||||
status_msg "Stopping KlipperScreen Service ..."
|
||||
sudo systemctl stop KlipperScreen && ok_msg "KlipperScreen Service stopped!"
|
||||
}
|
||||
|
||||
restart_klipperscreen(){
|
||||
status_msg "Restarting KlipperScreen Service ..."
|
||||
sudo systemctl restart KlipperScreen && ok_msg "KlipperScreen Service restarted!"
|
||||
}
|
||||
|
||||
start_MoonrakerTelegramBot(){
|
||||
status_msg "Starting MoonrakerTelegramBot Service ..."
|
||||
sudo systemctl start moonraker-telegram-bot && ok_msg "MoonrakerTelegramBot Service started!"
|
||||
}
|
||||
|
||||
stop_MoonrakerTelegramBot(){
|
||||
status_msg "Stopping MoonrakerTelegramBot Service ..."
|
||||
sudo systemctl stop moonraker-telegram-bot && ok_msg "MoonrakerTelegramBot Service stopped!"
|
||||
}
|
||||
|
||||
restart_MoonrakerTelegramBot(){
|
||||
status_msg "Restarting MoonrakerTelegramBot Service ..."
|
||||
sudo systemctl restart moonraker-telegram-bot && ok_msg "MoonrakerTelegramBot Service restarted!"
|
||||
}
|
||||
|
||||
restart_nginx(){
|
||||
if ls /lib/systemd/system/nginx.service 2>/dev/null 1>&2; then
|
||||
status_msg "Restarting NGINX Service ..."
|
||||
sudo systemctl restart nginx && ok_msg "NGINX Service restarted!"
|
||||
fi
|
||||
}
|
||||
|
||||
dependency_check(){
|
||||
status_msg "Checking for the following dependencies:"
|
||||
#check if package is installed, if not write name into array
|
||||
for pkg in "${dep[@]}"
|
||||
do
|
||||
echo -e "${cyan}● $pkg ${default}"
|
||||
if [[ ! $(dpkg-query -f'${Status}' --show $pkg 2>/dev/null) = *\ installed ]]; then
|
||||
inst+=($pkg)
|
||||
fi
|
||||
done
|
||||
#if array is not empty, install packages from array elements
|
||||
if [ "${#inst[@]}" != "0" ]; then
|
||||
status_msg "Installing the following dependencies:"
|
||||
for element in ${inst[@]}
|
||||
do
|
||||
echo -e "${cyan}● $element ${default}"
|
||||
done
|
||||
echo
|
||||
sudo apt-get update --allow-releaseinfo-change && sudo apt-get install ${inst[@]} -y
|
||||
ok_msg "Dependencies installed!"
|
||||
#clearing the array
|
||||
unset inst
|
||||
else
|
||||
ok_msg "Dependencies already met! Continue..."
|
||||
fi
|
||||
}
|
||||
|
||||
print_error(){
|
||||
for data in "${data_arr[@]}"
|
||||
do
|
||||
if [ ! -e $data ]; then
|
||||
data_count+=(0)
|
||||
else
|
||||
data_count+=(1)
|
||||
fi
|
||||
done
|
||||
sum=$(IFS=+; echo "$((${data_count[*]}))")
|
||||
if [ $sum -eq 0 ]; then
|
||||
ERROR_MSG="Looks like $1 was already removed!\n Skipping..."
|
||||
else
|
||||
ERROR_MSG=""
|
||||
fi
|
||||
}
|
||||
|
||||
setup_gcode_shell_command(){
|
||||
echo
|
||||
top_border
|
||||
echo -e "| You are about to install the G-Code Shell Command |"
|
||||
echo -e "| extension. Please make sure to read the instructions |"
|
||||
echo -e "| before you continue and remember that potential risks |"
|
||||
echo -e "| can be involved after installing this extension! |"
|
||||
blank_line
|
||||
echo -e "| ${red}You accept that you are doing this on your own risk!${default} |"
|
||||
bottom_border
|
||||
while true; do
|
||||
read -p "${cyan}###### Do you want to continue? (Y/n):${default} " yn
|
||||
case "$yn" in
|
||||
Y|y|Yes|yes|"")
|
||||
if [ -d $KLIPPER_DIR/klippy/extras ]; then
|
||||
status_msg "Installing gcode shell command extension ..."
|
||||
if [ -f $KLIPPER_DIR/klippy/extras/gcode_shell_command.py ]; then
|
||||
warn_msg "There is already a file named 'gcode_shell_command.py'\nin the destination location!"
|
||||
while true; do
|
||||
read -p "${cyan}###### Do you want to overwrite it? (Y/n):${default} " yn
|
||||
case "$yn" in
|
||||
Y|y|Yes|yes|"")
|
||||
rm -f $KLIPPER_DIR/klippy/extras/gcode_shell_command.py
|
||||
install_gcode_shell_command
|
||||
break;;
|
||||
N|n|No|no)
|
||||
break;;
|
||||
esac
|
||||
done
|
||||
else
|
||||
install_gcode_shell_command
|
||||
fi
|
||||
else
|
||||
ERROR_MSG="Folder ~/klipper/klippy/extras not found!"
|
||||
fi
|
||||
break;;
|
||||
N|n|No|no)
|
||||
break;;
|
||||
*)
|
||||
print_unkown_cmd
|
||||
print_msg && clear_msg;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
install_gcode_shell_command(){
|
||||
do_action_service "stop" "klipper"
|
||||
status_msg "Copy 'gcode_shell_command.py' to '$KLIPPER_DIR/klippy/extras' ..."
|
||||
cp ${SRCDIR}/kiauh/resources/gcode_shell_command.py $KLIPPER_DIR/klippy/extras
|
||||
while true; do
|
||||
echo
|
||||
read -p "${cyan}###### Do you want to create the example shell command? (Y/n):${default} " yn
|
||||
case "$yn" in
|
||||
Y|y|Yes|yes|"")
|
||||
status_msg "Copy shell_command.cfg ..."
|
||||
### create a backup of the config folder
|
||||
backup_klipper_config_dir
|
||||
|
||||
### handle single printer.cfg
|
||||
if [ -f $klipper_cfg_loc/printer.cfg ] && [ ! -f $klipper_cfg_loc/shell_command.cfg ]; then
|
||||
### copy shell_command.cfg to config location
|
||||
cp ${SRCDIR}/kiauh/resources/shell_command.cfg $klipper_cfg_loc
|
||||
ok_msg "$klipper_cfg_loc/shell_command.cfg created!"
|
||||
|
||||
### write the include to the very first line of the printer.cfg
|
||||
sed -i "1 i [include shell_command.cfg]" $klipper_cfg_loc/printer.cfg
|
||||
fi
|
||||
|
||||
### handle multi printer.cfg
|
||||
if ls $klipper_cfg_loc/printer_* 2>/dev/null 1>&2; then
|
||||
for config in $(find $klipper_cfg_loc/printer_*/printer.cfg); do
|
||||
path=$(echo $config | rev | cut -d"/" -f2- | rev)
|
||||
if [ ! -f $path/shell_command.cfg ]; then
|
||||
### copy shell_command.cfg to config location
|
||||
cp ${SRCDIR}/kiauh/resources/shell_command.cfg $path
|
||||
ok_msg "$path/shell_command.cfg created!"
|
||||
|
||||
### write the include to the very first line of the printer.cfg
|
||||
sed -i "1 i [include shell_command.cfg]" $path/printer.cfg
|
||||
fi
|
||||
done
|
||||
fi
|
||||
break;;
|
||||
N|n|No|no)
|
||||
break;;
|
||||
esac
|
||||
done
|
||||
ok_msg "Shell command extension installed!"
|
||||
do_action_service "restart" "klipper"
|
||||
}
|
||||
|
||||
create_minimal_cfg(){
|
||||
#create a minimal default config
|
||||
if [ "$SEL_DEF_CFG" = "true" ]; then
|
||||
cat <<- EOF >> $PRINTER_CFG
|
||||
[mcu]
|
||||
serial: </dev/serial/by-id/your-mcu>
|
||||
|
||||
[printer]
|
||||
kinematics: none
|
||||
max_velocity: 1
|
||||
max_accel: 1
|
||||
|
||||
[virtual_sdcard]
|
||||
path: ~/sdcard
|
||||
|
||||
[pause_resume]
|
||||
[display_status]
|
||||
EOF
|
||||
fi
|
||||
}
|
||||
|
||||
init_ini(){
|
||||
### copy an existing kiauh.ini to its new location to keep all possible saved values
|
||||
if [ -f ${SRCDIR}/kiauh/kiauh.ini ] && [ ! -f $INI_FILE ]; then
|
||||
cp ${SRCDIR}/kiauh/kiauh.ini $INI_FILE
|
||||
fi
|
||||
if [ ! -f $INI_FILE ]; then
|
||||
echo -e "#don't edit this file if you don't know what you are doing...\c" > $INI_FILE
|
||||
fi
|
||||
if [ ! $(grep -E "^backup_before_update=." $INI_FILE) ]; then
|
||||
echo -e "\nbackup_before_update=false\c" >> $INI_FILE
|
||||
fi
|
||||
if [ ! $(grep -E "^previous_origin_state=[[:alnum:]]" $INI_FILE) ]; then
|
||||
echo -e "\nprevious_origin_state=0\c" >> $INI_FILE
|
||||
fi
|
||||
if [ ! $(grep -E "^previous_smoothing_state=[[:alnum:]]" $INI_FILE) ]; then
|
||||
echo -e "\nprevious_smoothing_state=0\c" >> $INI_FILE
|
||||
fi
|
||||
if [ ! $(grep -E "^previous_shaping_state=[[:alnum:]]" $INI_FILE) ]; then
|
||||
echo -e "\nprevious_shaping_state=0\c" >> $INI_FILE
|
||||
fi
|
||||
if [ ! $(grep -E "^logupload_accepted=." $INI_FILE) ]; then
|
||||
echo -e "\nlogupload_accepted=false\c" >> $INI_FILE
|
||||
fi
|
||||
###add empty klipper config path if missing
|
||||
if [ ! $(grep -E "^klipper_cfg_loc=" $INI_FILE) ]; then
|
||||
echo -e "\nklipper_cfg_loc=\c" >> $INI_FILE
|
||||
fi
|
||||
fetch_webui_ports
|
||||
}
|
||||
123
scripts/gcode_shell_command.sh
Normal file
123
scripts/gcode_shell_command.sh
Normal file
@@ -0,0 +1,123 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
#=======================================================================#
|
||||
# Copyright (C) 2020 - 2023 Dominik Willner <th33xitus@gmail.com> #
|
||||
# #
|
||||
# This file is part of KIAUH - Klipper Installation And Update Helper #
|
||||
# https://github.com/dw-0/kiauh #
|
||||
# #
|
||||
# This file may be distributed under the terms of the GNU GPLv3 license #
|
||||
#=======================================================================#
|
||||
|
||||
set -e
|
||||
|
||||
#=================================================#
|
||||
#======== INSTALL GCODE_SHELL_COMMAND.PY =========#
|
||||
#=================================================#
|
||||
|
||||
function setup_gcode_shell_command() {
|
||||
top_border
|
||||
echo -e "| You are about to install the 'G-Code Shell Command' |"
|
||||
echo -e "| extension. Please make sure to read the instructions |"
|
||||
echo -e "| before you continue and remember that potential risks |"
|
||||
echo -e "| can be involved after installing this extension! |"
|
||||
blank_line
|
||||
echo -e "| ${red}You accept that you are doing this on your own risk!${white} |"
|
||||
bottom_border
|
||||
|
||||
local yn
|
||||
while true; do
|
||||
read -p "${cyan}###### Do you want to continue? (Y/n):${white} " yn
|
||||
case "${yn}" in
|
||||
Y|y|Yes|yes|"")
|
||||
select_msg "Yes"
|
||||
|
||||
if [[ ! -d "${KLIPPER_DIR}/klippy/extras" ]]; then
|
||||
print_error "Folder ~/klipper/klippy/extras not found!\n Klipper not installed yet?"
|
||||
return
|
||||
fi
|
||||
|
||||
status_msg "Installing gcode shell command extension ..."
|
||||
|
||||
if [[ ! -f "${KLIPPER_DIR}/klippy/extras/gcode_shell_command.py" ]]; then
|
||||
install_gcode_shell_command
|
||||
else
|
||||
echo; warn_msg "File 'gcode_shell_command.py' already exists in the destination location!"
|
||||
|
||||
while true; do
|
||||
read -p "${cyan}###### Do you want to overwrite it? (Y/n):${white} " yn
|
||||
case "${yn}" in
|
||||
Y|y|Yes|yes|"")
|
||||
select_msg "Yes"
|
||||
rm -f "${KLIPPER_DIR}/klippy/extras/gcode_shell_command.py"
|
||||
install_gcode_shell_command
|
||||
break;;
|
||||
N|n|No|no)
|
||||
select_msg "No"
|
||||
break;;
|
||||
*)
|
||||
error_msg "Invalid Input!";;
|
||||
esac
|
||||
done
|
||||
fi
|
||||
return;;
|
||||
N|n|No|no)
|
||||
select_msg "No"
|
||||
return;;
|
||||
*)
|
||||
error_msg "Invalid Input!";;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
function install_gcode_shell_command() {
|
||||
do_action_service "stop" "klipper"
|
||||
status_msg "Copy 'gcode_shell_command.py' to '${KLIPPER_DIR}/klippy/extras' ..."
|
||||
|
||||
if cp "${KIAUH_SRCDIR}/resources/gcode_shell_command.py" "${KLIPPER_DIR}/klippy/extras"; then
|
||||
ok_msg "Done!"
|
||||
else
|
||||
error_msg "Cannot copy file to target destination...Exiting!"
|
||||
return 1
|
||||
fi
|
||||
|
||||
local yn
|
||||
while true; do
|
||||
echo
|
||||
read -p "${cyan}###### Create an example shell command? (Y/n):${white} " yn
|
||||
case "${yn}" in
|
||||
Y|y|Yes|yes|"")
|
||||
select_msg "Yes"
|
||||
create_example_shell_command
|
||||
break;;
|
||||
N|n|No|no)
|
||||
select_msg "No"
|
||||
break;;
|
||||
esac
|
||||
done
|
||||
|
||||
do_action_service "restart" "klipper"
|
||||
print_confirm "Shell command extension installed!"
|
||||
return
|
||||
}
|
||||
|
||||
function create_example_shell_command() {
|
||||
### create a backup of the config folder
|
||||
backup_config_dir
|
||||
|
||||
local configs regex path
|
||||
regex="${HOME//\//\\/}\/([A-Za-z0-9_]+)\/config\/printer\.cfg"
|
||||
configs=$(find "${HOME}" -maxdepth 3 -regextype posix-extended -regex "${regex}" | sort)
|
||||
|
||||
for cfg in ${configs}; do
|
||||
path=$(echo "${cfg}" | rev | cut -d"/" -f2- | rev)
|
||||
|
||||
if [[ ! -f "${path}/shell_command.cfg" ]]; then
|
||||
status_msg "Copy shell_command.cfg to ${path} ..."
|
||||
cp "${KIAUH_SRCDIR}/resources/shell_command.cfg" "${path}"
|
||||
ok_msg "${path}/shell_command.cfg created!"
|
||||
### write include to the very first line of the printer.cfg
|
||||
sed -i "1 i [include shell_command.cfg]" "${cfg}"
|
||||
fi
|
||||
done
|
||||
}
|
||||
86
scripts/globals.sh
Normal file
86
scripts/globals.sh
Normal file
@@ -0,0 +1,86 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
#=======================================================================#
|
||||
# Copyright (C) 2020 - 2023 Dominik Willner <th33xitus@gmail.com> #
|
||||
# #
|
||||
# This file is part of KIAUH - Klipper Installation And Update Helper #
|
||||
# https://github.com/dw-0/kiauh #
|
||||
# #
|
||||
# This file may be distributed under the terms of the GNU GPLv3 license #
|
||||
#=======================================================================#
|
||||
|
||||
# shellcheck disable=SC2034
|
||||
set -e
|
||||
|
||||
function set_globals() {
|
||||
#=================== SYSTEM ===================#
|
||||
SYSTEMD="/etc/systemd/system"
|
||||
INITD="/etc/init.d"
|
||||
ETCDEF="/etc/default"
|
||||
|
||||
#=================== KIAUH ====================#
|
||||
green=$(echo -en "\e[92m")
|
||||
yellow=$(echo -en "\e[93m")
|
||||
magenta=$(echo -en "\e[35m")
|
||||
red=$(echo -en "\e[91m")
|
||||
cyan=$(echo -en "\e[96m")
|
||||
white=$(echo -en "\e[39m")
|
||||
INI_FILE="${HOME}/.kiauh.ini"
|
||||
LOGFILE="/tmp/kiauh.log"
|
||||
RESOURCES="${KIAUH_SRCDIR}/resources"
|
||||
BACKUP_DIR="${HOME}/kiauh-backups"
|
||||
|
||||
#================== KLIPPER ===================#
|
||||
KLIPPY_ENV="${HOME}/klippy-env"
|
||||
KLIPPER_DIR="${HOME}/klipper"
|
||||
KLIPPER_REPO="https://github.com/Klipper3d/klipper.git"
|
||||
|
||||
#================= MOONRAKER ==================#
|
||||
MOONRAKER_ENV="${HOME}/moonraker-env"
|
||||
MOONRAKER_DIR="${HOME}/moonraker"
|
||||
MOONRAKER_REPO="https://github.com/Arksine/moonraker.git"
|
||||
|
||||
#================= MAINSAIL ===================#
|
||||
MAINSAIL_DIR="${HOME}/mainsail"
|
||||
|
||||
#================== FLUIDD ====================#
|
||||
FLUIDD_DIR="${HOME}/fluidd"
|
||||
|
||||
#=============== KLIPPERSCREEN ================#
|
||||
KLIPPERSCREEN_ENV="${HOME}/.KlipperScreen-env"
|
||||
KLIPPERSCREEN_DIR="${HOME}/KlipperScreen"
|
||||
KLIPPERSCREEN_REPO="https://github.com/jordanruthe/KlipperScreen.git"
|
||||
|
||||
#========== MOONRAKER-TELEGRAM-BOT ============#
|
||||
TELEGRAM_BOT_ENV="${HOME}/moonraker-telegram-bot-env"
|
||||
TELEGRAM_BOT_DIR="${HOME}/moonraker-telegram-bot"
|
||||
TELEGRAM_BOT_REPO="https://github.com/nlef/moonraker-telegram-bot.git"
|
||||
|
||||
#=============== PRETTY-GCODE =================#
|
||||
PGC_DIR="${HOME}/pgcode"
|
||||
PGC_REPO="https://github.com/Kragrathea/pgcode"
|
||||
|
||||
#================== NGINX =====================#
|
||||
NGINX_SA="/etc/nginx/sites-available"
|
||||
NGINX_SE="/etc/nginx/sites-enabled"
|
||||
NGINX_CONFD="/etc/nginx/conf.d"
|
||||
|
||||
#=============== MOONRAKER-OBICO ================#
|
||||
MOONRAKER_OBICO_DIR="${HOME}/moonraker-obico"
|
||||
MOONRAKER_OBICO_REPO="https://github.com/TheSpaghettiDetective/moonraker-obico.git"
|
||||
|
||||
#=============== OCTOEVERYWHERE ================#
|
||||
OCTOEVERYWHERE_ENV="${HOME}/octoeverywhere-env"
|
||||
OCTOEVERYWHERE_DIR="${HOME}/octoeverywhere"
|
||||
OCTOEVERYWHERE_REPO="https://github.com/QuinnDamerell/OctoPrint-OctoEverywhere.git"
|
||||
|
||||
#=============== Crowsnest ================#
|
||||
CROWSNEST_DIR="${HOME}/crowsnest"
|
||||
CROWSNEST_REPO="https://github.com/mainsail-crew/crowsnest.git"
|
||||
|
||||
#=============== Mobileraker ================#
|
||||
MOBILERAKER_ENV="${HOME}/mobileraker-env"
|
||||
MOBILERAKER_DIR="${HOME}/mobileraker_companion"
|
||||
MOBILERAKER_REPO="https://github.com/Clon1998/mobileraker_companion.git"
|
||||
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user