Compare commits

...

137 Commits

Author SHA1 Message Date
dw-0
4915896099 feat(Mainsail): remove Mainsail
Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2023-12-21 22:53:41 +01:00
dw-0
cd38970bbd refactor(Mainsail): move some functions to a mainsail utils module
Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2023-12-20 22:35:29 +01:00
dw-0
b8640f45a6 refactor(Klipper): refactor example printer.cfg creation
Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2023-12-17 23:30:38 +01:00
dw-0
5fb4444f03 refactor(Moonraker): refactor example moonraker.conf creation
Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2023-12-17 23:30:15 +01:00
dw-0
926ba1acb4 feat(ConfigManager): implement own ConfigParser write() method
Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2023-12-17 21:49:09 +01:00
dw-0
c2e7ee98df feat(Mainsail): implement Mainsail installer
Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2023-12-17 18:08:18 +01:00
dw-0
3865266da1 refactor(RepoManager): default to master branch if none is provided
Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2023-12-17 18:07:18 +01:00
dw-0
b83f642a13 refactor(ConfigManager): logging can be silenced
Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2023-12-17 18:03:42 +01:00
dw-0
30b4414469 feat(Klipper): create example printer.cfg if wanted
Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2023-12-17 18:00:16 +01:00
dw-0
1178d3c730 refactor(Moonraker): skip selection dialog if there is only 1 klipper instance
Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2023-12-17 16:18:44 +01:00
dw-0
59d8867c8c fix(kiauh): copy&paste issue in repo url for Moonraker
Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2023-12-17 16:12:43 +01:00
dw-0
80a953a587 fix(Moonraker): typo in python version check
Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2023-12-17 16:10:20 +01:00
dw-0
a80f0bb0e8 feat(utils): add several util methods
Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2023-12-17 14:42:53 +01:00
dw-0
78cefddb2e feat(InstanceManager): add restart service method
Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2023-12-17 13:26:22 +01:00
dw-0
b20613819e feat(Logger): add "start" parameter
Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2023-12-17 13:00:46 +01:00
dw-0
1836beab42 feat(klipper): add getter for specific properties
Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2023-12-17 00:01:25 +01:00
dw-0
545397f598 feat(kiauh): fix typo in check_package_install
Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2023-12-16 17:38:01 +01:00
dw-0
f709cf84e7 feat(kiauh): add helper methods for downloading files
Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2023-12-16 15:41:48 +01:00
dw-0
f62c10dc8b feat(kiauh): add helper methods to check for installed packages
Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2023-12-16 15:38:23 +01:00
dw-0
e121ba8a62 feat(Moonraker): add python version check
Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2023-12-04 22:35:36 +01:00
dw-0
9a1a66aa64 docs(utils): add docstrings
Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2023-12-04 21:35:28 +01:00
dw-0
420b193f4b feat(Moonraker): implement Moonraker
Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2023-12-03 23:13:28 +01:00
dw-0
de20f0c412 refactor(ConfigManager): allow to take in any config file
Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2023-12-03 23:10:43 +01:00
dw-0
57f34b07c6 refactor(utils): add more util functions
Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2023-12-03 23:09:43 +01:00
dw-0
e35e44a76a refactor(kiauh): move create_folders to BaseInstance
Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2023-12-03 23:08:41 +01:00
dw-0
bfb10c742b refactor(kiauh): reword print_info to print_status and implement new print_info method
Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2023-12-03 23:06:30 +01:00
dw-0
458c89a78a fix(InstanceManager): print service name instead of suffix only
Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2023-12-02 16:52:30 +01:00
dw-0
6128e35d45 refactor(kiauh): rework menu formatting logic
Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2023-11-15 22:58:54 +01:00
dw-0
279d000bb0 refactor(kiauh): specify python3 in shebang
Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2023-11-14 21:31:25 +01:00
dw-0
a4a3d5eecb feat(BackupManager): implement simple backup manager
Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2023-11-14 21:28:13 +01:00
dw-0
1392ca9f82 refactor(klipper): pass the script path as a Path to the parse function
Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2023-11-13 20:07:21 +01:00
dw-0
47121f6875 refactor(utils): clean up, add comments
Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2023-11-13 20:06:48 +01:00
dw-0
d0d2404132 refactor(kiauh): move core modules to core package
Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2023-11-12 23:28:05 +01:00
dw-0
6ed5395f17 feat(klipper): check for brltty-udev too
Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2023-11-12 01:26:43 +01:00
dw-0
be805c169b feat(klipper): allow keeping klipper and klipper-env dir during uninstall
Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2023-11-12 01:13:39 +01:00
dw-0
eaf12db27e fix(klipper): allow go back when asked for new instances
Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2023-11-12 00:39:10 +01:00
dw-0
fe8767113b refactor(klipper): rework dialogs
Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2023-11-12 00:37:25 +01:00
dw-0
2148d95cf4 fix(InstanceManager): return None for suffix if there is none
Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2023-11-12 00:10:52 +01:00
dw-0
682be48e8d fix(InstanceManager): instance_service should be service file name
remove debug prints

Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2023-11-12 00:01:21 +01:00
dw-0
68369753fd refactor(InstanceManager): rework
Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2023-11-11 23:56:27 +01:00
dw-0
44ed3b6ddf feat(kiauh): add .iml to gitignore
Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2023-11-11 23:53:42 +01:00
dw-0
e12e578098 refactor(klipper): rewrite dialogs
Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2023-11-05 23:13:16 +01:00
dw-0
515a42f098 feat(klipper): implement update function
Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2023-11-05 16:15:19 +01:00
dw-0
f9ecad0eca refactor(klipper): use name "klipper" for single instance setup
Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2023-11-05 16:12:17 +01:00
dw-0
fb09acf660 refactor(utils): reduce complexity
Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2023-11-02 22:52:34 +01:00
dw-0
093da73dd1 refactor(klipper): use constants for commonly used strings
Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2023-11-02 22:37:17 +01:00
dw-0
c9e8c4807e feat(klipper): convert single to multi instance
Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2023-10-31 20:54:44 +01:00
dw-0
09e874214b feat(ConfigManager): implement ConfigManager
Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2023-10-29 17:19:26 +01:00
dw-0
623bd7553b feat(RepoManager): implement RepoManager
Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2023-10-29 13:25:21 +01:00
dw-0
1e0c74b549 style: rename input functions
Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2023-10-29 01:29:27 +02:00
dw-0
358c666da9 feat(style): use black code style / formatter
Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2023-10-29 00:31:34 +02:00
dw-0
84a530be7d fix(klipper): handle disruptive system packages/services
Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2023-10-28 23:52:51 +02:00
dw-0
bfff3019cb fix(InstanceManager): fix TypeError if instance name is None
Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2023-10-28 22:10:07 +02:00
dw-0
2a100c2934 feat(klipper): check for required user-groups
Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2023-10-28 21:31:31 +02:00
dw-0
ce0daa52ae feat(klipper): implement instance manager and klipper installer in python
Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2023-10-26 13:58:31 +02:00
th33xitus
f45da66e9e fix(crowsnest): uninstaller exited KIAUH if crowsnest not installed
Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2023-08-26 18:31:37 +02:00
th33xitus
2822499344 refactor(copyright): update copyright comment
Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2023-08-26 18:11:00 +02:00
th33xitus
c777ba3e6b refactor(log-upload): update log upload functions and re-enable access
Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2023-08-26 18:06:14 +02:00
th33xitus
9f410450d7 refactor(backups): update backup functions for config and moonraker database and enable backup-before-update again
Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2023-08-23 23:22:23 +02:00
dw-0
0497d49066 Update README.md 2023-08-15 16:42:38 +02:00
dw-0
229da227b0 Update FUNDING.yml
use updated ko-fi username
2023-08-14 23:06:44 +02:00
th33xitus
65854c8da6 fix(updates): make update all function also update system again
Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2023-07-22 23:24:29 +02:00
Kenneth Jiang
5985646633 refactor(obico): move dependency handling to obico's own install script (#361) 2023-07-10 17:44:05 +02:00
th33xitus
979c39dc02 refactor(mainsail/fluidd): allow reading of version from release_info.json
Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2023-07-01 20:59:46 +02:00
th33xitus
197058bd00 changelog: update Changelog
Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2023-06-17 21:19:49 +02:00
th33xitus
d3b5122ebb refactor(UI): move version and added changelog link to own row in main menu
Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2023-06-17 21:11:23 +02:00
th33xitus
8ce4daf403 refactor(klipper): pre-select python3 for klipper install
Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2023-06-17 19:51:41 +02:00
th33xitus
b0a65fe14e refactor(UI): remove warn message
more than half a year is gone, the warning should not be that important anymore

Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2023-06-17 19:49:57 +02:00
Patrick Schmidt
98866caefa feat: add Mobileraker (#343)
Co-authored-by: th33xitus <th33xitus@googlemail.com>
2023-06-17 19:34:26 +02:00
phizev
345b7b66a3 refactor: use service specific directories in templates (#355) 2023-06-17 18:22:16 +02:00
cravl-dev
8eb2924832 refactor: update package lists only when stale (#346)
Co-authored-by: th33xitus <th33xitus@googlemail.com>
2023-06-17 18:08:24 +02:00
Kyriel Abad
5d7debd65e readme: fix typos in README.md (#352) 2023-06-10 23:00:23 +02:00
th33xitus
7df3dd489f fix(mainsail/fluidd): show correct version number in update menu
fixes #350

Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2023-06-07 21:48:17 +02:00
marbocub
0cd058320f feat: allow to install Mainsail/Fluidd without Moonraker (#347)
Co-authored-by: th33xitus <th33xitus@googlemail.com>
2023-06-06 21:09:47 +02:00
th33xitus
bcbb185bd7 readme: update README.md
also fixes #327
2023-06-04 21:57:56 +02:00
CODeRUS
477f3ca72c feat: flash DFU device in HID mode (#337)
Co-authored-by: th33xitus <th33xitus@googlemail.com>
2023-06-04 21:23:32 +02:00
th33xitus
c19acb1694 fix(mainsail/fluidd): fall back to latest stable url if fetching tags fails (#348) 2023-06-04 19:12:53 +02:00
Jookia
8228943850 fix(klipper): if set, use custom branch to check for update (#332) 2023-04-16 08:56:18 +02:00
Stefan Dej
5b890fb0fb refactor(mainsail): update themes.csv url for mainsail themes (#329) 2023-04-13 17:59:14 +02:00
Piotr Banasik
7989cec8d4 fix(crowsnest): override installers BASE_USER to current user (#317) 2023-03-19 11:50:06 +01:00
th33xitus
858301aa9a fix: temporary disable backup_before_update
Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2023-03-18 18:15:11 +01:00
Quinn Damerell
ae9e79c579 feat(octoeverywhere): add OE to the "update all" action (#311) 2023-03-11 23:52:46 +01:00
th33xitus
1215446a6c feat(octoeverywhere): implement update function (#310) 2023-03-11 20:25:17 +01:00
th33xitus
8526acf8b6 chore: update copyright notice
Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2023-03-10 20:06:06 +01:00
th33xitus
cc27aaec7c readme: add prerequisites to readme (#309) 2023-03-08 20:07:00 +01:00
Quinn Damerell
1e9493461c feat: add OctoEverywhere for Klipper (#300)
Co-authored-by: th33xitus <th33xitus@googlemail.com>
2023-03-05 17:19:20 +01:00
th33xitus
31616ebad5 fix(crowsnest): silence grep error output in main menu
fixes #308

Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2023-03-04 16:01:06 +01:00
Stephan Wendel
faf56ed1b1 refactor(crowsnest): improve performance in crowsnest.sh (#306) 2023-02-28 18:37:43 +01:00
th33xitus
d6837af2a2 refactor(klipper): implement blacklisted service names (#304) 2023-02-28 18:29:21 +01:00
Pedro Lamas
afe6f7499a feat(fluidd): use fluidd-config repo for downloading fluidd macros (#305) 2023-02-28 16:33:32 +01:00
Pedro Lamas
e3ed223b5c fix: always use system home directory (#303) 2023-02-28 15:30:42 +01:00
th33xitus
fd27db28d4 feat(mainsail): use mainsail-config repo for downloading mainsail macros (#301) 2023-02-26 16:29:01 +01:00
Michael Bravo
68a02ad3f5 fix(mainsail): set increased read timeout on API endpoint (#294) 2023-02-25 12:59:31 +01:00
CODeRUS
99b7672dc9 fix(telegram bot): Fix service restart (#296) 2023-02-14 19:40:11 +01:00
th33xitus
bb3ec79756 revert(mjpg-streamer): re-add uninstall option for mjpg-streamer
fix #291

Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2023-02-05 08:57:15 +01:00
Kenneth Jiang
ce595abd60 refactor(obico): remove "-s" option from moonraker-obico/install.sh invocation (#292) 2023-02-05 08:50:06 +01:00
th33xitus
c79dc280e3 refactor(moonraker): update cors domains in moonraker.conf template
Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2023-02-05 08:47:46 +01:00
Stephan Wendel
7aa186e8b9 feat: replace mjpg-streamer with crowsnest (#286)
Co-authored-by: th33xitus <th33xitus@googlemail.com>
2023-02-03 21:59:59 +01:00
th33xitus
8493269c6f refactor: hide currently still disabled functions from the menus
Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2023-01-08 11:05:14 +01:00
th33xitus
150ef0142f refactor(mainsail/fluidd): move tag urls from globals to respective files
Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2022-12-14 19:40:12 +01:00
th33xitus
f70faa52cc fix(klipper): update python dialog description
Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2022-12-08 19:25:58 +01:00
CODeRUS
e796f74640 refactor(klipper): recommend Python3 for Klipper installation (#246) 2022-12-07 18:42:51 +01:00
Thomas Lété
2c9f5bed60 fix: pull mainsail.cfg from correct location (#272) 2022-12-07 17:34:01 +01:00
th33xitus
e9c23ca93e fix(klipper): use correct value for py_ver variable in update function
Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2022-11-14 19:50:55 +01:00
th33xitus
67afa26ed7 refactor(klipper): full refactor of klipper install/remove part (#264) 2022-11-13 15:51:22 +01:00
th33xitus
54be7e4e21 refactor(obico): update obico installer (#254) 2022-10-31 16:06:45 +01:00
th33xitus
811c071b74 refactor(telegram-bot): update telegram-bot installer (#251) 2022-10-30 18:30:20 +01:00
th33xitus
6116fc92cf fix: allow for any amount of whitespaces after config section in update manager
Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2022-10-30 18:27:13 +01:00
th33xitus
5524a40f04 refactor(klipperscreen): re-enable klipperscreen installer
Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2022-10-25 15:24:25 +02:00
th33xitus
cb3661b8b5 refactor(prettygcode): re-enable pretty gcode installer
Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2022-10-25 15:23:57 +02:00
th33xitus
2cec90b29c refactor(octoprint): update octoprint installer
Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2022-10-24 10:35:30 +02:00
th33xitus
d2c009df9a fix(klipper): network.target is now network-online.target
Signed-off-by: Dominik Willner th33xitus@gmail.com
2022-10-22 23:28:01 +02:00
th33xitus
046178f801 refactor!: update KIAUH for recent moonraker changes (#245) 2022-10-20 11:20:34 +02:00
cobyn
442980dbd0 fix(flash_klipper): spelling mistake (#242) 2022-10-05 17:22:31 +02:00
th33xitus
798e56f4dc refactor(mainsail): replace deprecated remote mode with new instanceDB
Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2022-09-18 23:45:02 +02:00
th33xitus
9d90daec7f fix(switch_klipper_repo): use of case didn't allow selections of 2 and above
Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2022-09-10 20:25:05 +02:00
megapro17
f25726cfed fix: typo in mjpg-streamer dialog (#239) 2022-09-08 09:15:24 +02:00
th33xitus
f46b099b74 refactor(klipper): more verbose error message if SysVinit script is detected
Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2022-09-03 14:14:37 +02:00
th33xitus
03be46f012 refactor(moonraker): skip polkit script execution if polkit rules already installed
Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2022-09-03 13:41:02 +02:00
th33xitus
c11e628c55 fix(moonraker): do not exit if moonraker_polkit exits with an error during moonraker update
Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2022-09-03 11:34:45 +02:00
th33xitus
4c8d43e365 refactor: use OS independent method to check/set for correct home folder permissions
Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2022-08-27 22:08:01 +02:00
Kenneth Jiang
9d7144b493 feat: add Obico for Klipper to KIAUH (#227)
Co-authored-by: th33xitus <th33xitus@googlemail.com>
2022-08-15 19:44:04 +02:00
th33xitus
6df388f42b refactor: install webcamd from self provided resources
with the upcoming release of a new MainsailOS image, mjpg-streamer is replaced by crowsnest. it is therefore not possible anymore to pull the files from the mainsailOS repository. hence adding them to the KIAUH repository.

Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2022-07-29 19:46:39 +02:00
th33xitus
1d7fb010af refactor: remove unused kiauh_macros.cfg
Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2022-07-24 08:51:48 +02:00
th33xitus
d4207d710c fix: restart moonraker after moonraker.conf patch (fixes #225)
Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2022-07-22 10:16:00 +02:00
th33xitus
6cb8d70b63 fix: replace generic nginx webui cfg by their respective ones
Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2022-07-22 10:06:35 +02:00
th33xitus
ae011963da fix: install mainsail theme to correct folder (fixes #222)
Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2022-07-20 22:41:42 +02:00
th33xitus
491d6f40bb refactor: move ini initialization to main_menu()
Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2022-07-20 22:34:07 +02:00
th33xitus
8bbe2f79ea feat: save multi instance klipper names/identifier to kiauh.ini
Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2022-07-20 22:33:33 +02:00
th33xitus
0bdf61a714 fix: do not install py2 packages if venv is py3 (fixes #211)
Signed-off-by: Dominik Willner th33xitus@gmail.com
2022-06-30 20:41:36 +02:00
th33xitus
b07a83c8ad fix: update all function (#204) (#217)
* fix: update all function (fixes #204)
* fix: replace non-existing `print_unkown_cmd` function

Signed-off-by: Dominik Willner th33xitus@gmail.com
2022-06-30 11:08:09 +02:00
th33xitus
39e22acbed fix(octoprint.sh): script exiting without error message (fixes #216)
- the script was exiting without notifying the user that klipper has to be installed first

Signed-off-by: Dominik Willner th33xitus@gmail.com
2022-06-26 08:48:38 +02:00
th33xitus
8ba46fa4ac fix(pretty_gcode.sh): use main instead of master (fixes #203)
- pgc uses main instead of master. so checking for origin/master leads to an error

Signed-off-by: Dominik Willner th33xitus@gmail.com
2022-05-31 17:27:52 +02:00
th33xitus
d6b95c9d10 refactor(nginx.sh): refactor set_nginx_cfg()
- implements two dedicated nginx configs for mainsail and fluidd

Signed-off-by: Dominik Willner th33xitus@gmail.com
2022-05-30 18:38:45 +02:00
th33xitus
f3a769e03e README.md: reworded a feature description
Signed-off-by: Dominik Willner th33xitus@gmail.com
2022-05-29 22:02:03 +02:00
th33xitus
646e5acd3a README.md: update README.md
Signed-off-by: Dominik Willner th33xitus@gmail.com
2022-05-29 20:35:07 +02:00
th33xitus
fcf059df73 feat: KIAUH v4.0.0 (#191) 2022-05-29 20:11:15 +02:00
139 changed files with 13686 additions and 5984 deletions

2
.github/FUNDING.yml vendored
View File

@@ -3,7 +3,7 @@
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: # Replace with a single Patreon username patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective 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 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 community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username liberapay: # Replace with a single Liberapay username

7
.gitignore vendored
View File

@@ -1,2 +1,7 @@
.vscode
.idea .idea
.shellcheckrc .pytest_cache
.kiauh-env
*.code-workspace
*.iml
kiauh.cfg

15
.shellcheckrc Normal file
View 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
View File

@@ -1,72 +1,190 @@
![main_menu](resources/screenshots/kiauh.png) <p align="center">
# Klipper Installation And Update Helper <a>
![GitHub](https://img.shields.io/github/license/th33xitus/kiauh) ![GitHub Repo stars](https://img.shields.io/github/stars/th33xitus/kiauh) ![GitHub forks](https://img.shields.io/github/forks/th33xitus/kiauh) ![GitHub tag (latest SemVer)](https://img.shields.io/github/v/tag/th33xitus/kiauh) ![GitHub last commit](https://img.shields.io/github/last-commit/th33xitus/kiauh) ![GitHub contributors](https://img.shields.io/github/contributors/th33xitus/kiauh) <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!** **📢 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:** * **Step 2:** \
Once git is installed, use the following command to download KIAUH into your home-directory:
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:
```shell ```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 ./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. ### **📋 Please see the [Changelog](docs/changelog.md) for possible important changes!**
- **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.
### **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 <hr>
- Flash the MCU
- Read ID of the currently connected MCU
- and more ...
### **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) <tr>
- Other Debian based distributions can work <th><h3><a href="https://github.com/nlef/moonraker-telegram-bot">Moonraker-Telegram-Bot</a></h3></th>
- Reported to work on Armbian too <th><h3><a href="https://github.com/Kragrathea/pgcode">PrettyGCode for Klipper</a></h3></th>
- During the use of this script you might be asked for your sudo password. There are several functions involved which need sudo privileges. <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) <tr>
* 🌙[Moonraker](https://github.com/Arksine/moonraker) by [Arksine](https://github.com/Arksine) <th><h3><a href="https://github.com/Clon1998/mobileraker_companion">Mobileraker's Companion</a></h3></th>
* 💨[Mainsail](https://github.com/mainsail-crew/mainsail) by [mainsail-crew](https://github.com/mainsail-crew) <th><h3><a href="https://octoeverywhere.com/?source=kiauh_readme">OctoEverywhere For Klipper</a></h3></th>
* 🌊[Fluidd](https://github.com/fluidd-core/fluidd) by [fluidd-core](https://github.com/fluidd-core) <th><h3></h3></th>
* 🕸️[Duet Web Control](https://github.com/Duet3D/DuetWebControl) by [Duet3D](https://github.com/Duet3D) </tr>
* 🕸️[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)
## **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! * 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! * 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>

View File

@@ -2,6 +2,105 @@
This document covers possible important changes to KIAUH. 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 ### 2022-01-29
* Starting from the 28th of January, Moonraker can make use of PackageKit and PolicyKit.\ * Starting from the 28th of January, Moonraker can make use of PackageKit and PolicyKit.\
More details on that can be found [here]( 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-2.service
--> moonraker-n.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. * 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.

View File

@@ -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...

View File

@@ -41,7 +41,7 @@ Execute with:
`RUN_SHELL_COMMAND CMD=hello_world` `RUN_SHELL_COMMAND CMD=hello_world`
### Passing parameters: ### 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, 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`) 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. and writes the value of the parameter into a textfile called `test.txt` located in the home directory.

16
kiauh.cfg.example Normal file
View 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
View 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
View File

@@ -1,105 +1,108 @@
#!/bin/bash #!/usr/bin/env bash
clear
#=======================================================================#
# 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 set -e
clear
### set color variables function main() {
green=$(echo -en "\e[92m") local python_command
yellow=$(echo -en "\e[93m") local entrypoint
red=$(echo -en "\e[91m")
cyan=$(echo -en "\e[96m")
default=$(echo -en "\e[39m")
### sourcing all additional scripts if command -v python3 &>/dev/null; then
SRCDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )"/.. && pwd )" python_command="python3"
for script in "${SRCDIR}/kiauh/scripts/"*.sh; do . $script; done elif command -v python &>/dev/null; then
for script in "${SRCDIR}/kiauh/scripts/ui/"*.sh; do . $script; done python_command="python"
else
### set important directories echo "Python is not installed. Please install Python and try again."
#klipper exit 1
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}"
fi fi
entrypoint=$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")
${python_command} "${entrypoint}/kiauh.py"
} }
clear_msg(){ main
unset CONFIRM_MSG
unset ERROR_MSG
}
check_euid #### sourcing all additional scripts
init_ini #KIAUH_SRCDIR="$(dirname -- "$(readlink -f "${BASH_SOURCE[0]}")")"
kiauh_status #for script in "${KIAUH_SRCDIR}/scripts/"*.sh; do . "${script}"; done
main_menu #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
View 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
View File

View File

View 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!")

View File

View 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")

View File

View 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.system_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

View 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

View 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"

View 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="")

View 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()

View 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")

View 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="")

View File

@@ -0,0 +1,118 @@
#!/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,
4: self.remove_mainsail_config,
5: self.remove_fluidd,
6: self.remove_fluidd_config,
7: self.remove_klipperscreen,
8: self.remove_crowsnest,
9: self.remove_mjpgstreamer,
10: self.remove_pretty_gcode,
11: self.remove_telegram_bot,
12: self.remove_obico,
13: self.remove_octoeverywhere,
14: self.remove_mobileraker,
15: 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] | 8) [Crowsnest] |
| 2) [Moonraker] | 9) [MJPG-Streamer] |
| | |
| Klipper Webinterface: | Other: |
| 3) [Mainsail] | 10) [PrettyGCode] |
| 4) [Mainsail-Config] | 11) [Telegram Bot] |
| 5) [Fluidd] | 12) [Obico for Klipper] |
| 6) [Fluidd-Config] | 13) [OctoEverywhere] |
| | 14) [Mobileraker] |
| Touchscreen GUI: | 15) [NGINX] |
| 7) [KlipperScreen] | |
"""
)[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_mainsail(self):
mainsail_setup.run_mainsail_removal()
def remove_mainsail_config(self):
print("remove_mainsail_config")
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")

View 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")

View 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")

View File

View 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
View 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)

View File

View 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 ..."

View 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

View 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="")

View 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()

View 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}'")

View File

@@ -0,0 +1 @@
KLIPPER_ARGS="%KLIPPER_DIR%/klippy/klippy.py %CFG% -I %SERIAL% -l %LOG% -a %UDS%"

View 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

View File

@@ -1,14 +1,11 @@
[mcu] [mcu]
serial: /dev/serial/by-id/<your-mcu-id> serial: /dev/serial/by-id/<your-mcu-id>
[pause_resume]
[display_status]
[virtual_sdcard] [virtual_sdcard]
path: ~/gcode_files path: %GCODES_DIR%
on_error_gcode: CANCEL_PRINT
[printer] [printer]
kinematics: none kinematics: none
max_velocity: 1000 max_velocity: 1000
max_accel: 1000 max_accel: 1000

View 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"

View 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="")

View File

@@ -0,0 +1,150 @@
#!/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_ms_config_dir()
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).joinpath("mainsail"), True)
remove_file(Path(NGINX_SITES_ENABLED).joinpath("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).joinpath("mainsail-access.log"))
remove_file(Path(instance.log_dir).joinpath("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_ms_config_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_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()

View File

@@ -0,0 +1,228 @@
#!/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
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.common import check_install_dependencies
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,
unzip,
create_upstream_nginx_cfg,
create_nginx_cfg,
delete_default_nginx_cfg,
enable_nginx_cfg,
set_nginx_permissions,
get_ipv4_addr,
control_systemd_service,
create_common_vars_nginx_cfg,
)
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 is_klipper_installed and install_ms_config:
download_mainsail_config()
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()
create_upstream_nginx_cfg()
create_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_config() -> 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_nginx_cfg(port: int) -> None:
try:
Logger.print_status("Creating NGINX config for Mainsail ...")
root_dir = MAINSAIL_DIR
delete_default_nginx_cfg()
create_nginx_cfg("mainsail", port, root_dir)
enable_nginx_cfg("mainsail")
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]")

View 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)

View File

View 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

View 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

View File

@@ -0,0 +1,5 @@
[update_manager mainsail]
type: web
channel: stable
repo: mainsail-crew/mainsail
path: ~/mainsail

View 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 ..."

View 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

View 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()

View 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.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,
check_file_exists,
)
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_exists(Path(POLKIT_LEGACY_FILE))
polkit_file_exists = check_file_exists(Path(POLKIT_FILE))
usr_file_exists = check_file_exists(Path(POLKIT_USR_FILE))
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()

View 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}'")

View 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

View File

@@ -0,0 +1 @@
MOONRAKER_ARGS="%MOONRAKER_DIR%/moonraker/moonraker.py -d %PRINTER_DATA%"

View 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
View 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
View 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
View 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"

View 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 #
# ======================================================================= #
import subprocess
from pathlib import Path
from kiauh.utils.logger import Logger
def remove_file(file_path: Path, sudo=False) -> None:
try:
command = f"{'sudo ' if sudo else ''}rm -f {file_path}"
subprocess.run(command, 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

148
kiauh/utils/input_utils.py Normal file
View 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
View 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)

View 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
View 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/;
}
}

View 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;
}

475
kiauh/utils/system_utils.py Normal file
View File

@@ -0,0 +1,475 @@
#!/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 zipfile import ZipFile
from kiauh.utils import (
NGINX_CONFD,
MODULE_PATH,
NGINX_SITES_AVAILABLE,
NGINX_SITES_ENABLED,
)
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 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 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
def check_file_exists(file_path: Path) -> bool:
"""
Helper function for checking the existence of a file where
elevated permissions are required |
:param file_path: the absolute path of the file to check
:return: True if file exists, otherwise False
"""
try:
command = ["sudo", "find", file_path]
subprocess.check_output(command, stderr=subprocess.DEVNULL)
return True
except subprocess.CalledProcessError:
return False
# 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 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 create_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 create_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
def delete_default_nginx_cfg() -> None:
"""
Deletes a default NGINX config
:return: None
"""
default_cfg = Path("/etc/nginx/sites-enabled/default")
if not check_file_exists(default_cfg):
return
try:
command = ["sudo", "rm", default_cfg]
subprocess.run(command, stderr=subprocess.PIPE, check=True)
except subprocess.CalledProcessError as e:
log = f"Unable to delete '{default_cfg}': {e.stderr.decode()}"
Logger.print_error(log)
raise
def enable_nginx_cfg(name: str) -> None:
"""
Helper method to enable an NGINX config |
:param name: name of the config to enable
:return: None
"""
source = os.path.join(NGINX_SITES_AVAILABLE, name)
target = os.path.join(NGINX_SITES_ENABLED, name)
if check_file_exists(Path(target)):
return
try:
command = ["sudo", "ln", "-s", source, target]
subprocess.run(command, stderr=subprocess.PIPE, check=True)
except subprocess.CalledProcessError as e:
log = f"Unable to create symlink: {e.stderr.decode()}"
Logger.print_error(log)
raise
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
View File

@@ -0,0 +1,13 @@
[tool.black]
line-length = 88
target-version = ['py38']
include = '\.pyi?$'
exclude = '''
(
\.git/
| \.github/
| docs/
| resources/
| scripts/
)
'''

View File

@@ -1,10 +1,10 @@
#!/bin/bash #!/usr/bin/env bash
##################################################################### #####################################################################
### Please set the paths accordingly. In case you don't have all ### ### Please set the paths accordingly. In case you don't have all ###
### the listed folders, just keep that line commented out. ### ### 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 #config_folder=~/klipper_config
### Path to your Klipper folder, by default that is '~/klipper' ### Path to your Klipper folder, by default that is '~/klipper'
@@ -26,35 +26,38 @@
##################################################################### #####################################################################
################ !!! DO NOT EDIT BELOW THIS LINE !!! ################ ################ !!! DO NOT EDIT BELOW THIS LINE !!! ################
##################################################################### #####################################################################
grab_version(){ grab_version() {
if [ ! -z "$klipper_folder" ]; then local klipper_commit moonraker_commit
cd "$klipper_folder" local mainsail_ver fluidd_ver
if [[ -n ${klipper_folder} ]]; then
cd "${klipper_folder}"
klipper_commit=$(git rev-parse --short=7 HEAD) klipper_commit=$(git rev-parse --short=7 HEAD)
m1="Klipper on commit: $klipper_commit" m1="Klipper on commit: ${klipper_commit}"
cd ..
fi fi
if [ ! -z "$moonraker_folder" ]; then if [[ -n ${moonraker_folder} ]]; then
cd "$moonraker_folder" cd "${moonraker_folder}"
moonraker_commit=$(git rev-parse --short=7 HEAD) moonraker_commit=$(git rev-parse --short=7 HEAD)
m2="Moonraker on commit: $moonraker_commit" m2="Moonraker on commit: ${moonraker_commit}"
cd ..
fi fi
if [ ! -z "$mainsail_folder" ]; then if [[ -n ${mainsail_folder} ]]; then
mainsail_ver=$(head -n 1 $mainsail_folder/.version) mainsail_ver=$(head -n 1 "${mainsail_folder}/.version")
m3="Mainsail version: $mainsail_ver" m3="Mainsail version: ${mainsail_ver}"
fi fi
if [ ! -z "$fluidd_folder" ]; then if [[ -n ${fluidd_folder} ]]; then
fluidd_ver=$(head -n 1 $fluidd_folder/.version) fluidd_ver=$(head -n 1 "${fluidd_folder}/.version")
m4="Fluidd version: $fluidd_ver" m4="Fluidd version: ${fluidd_ver}"
fi fi
} }
push_config(){ push_config() {
cd $config_folder local current_date
cd "${config_folder}" || exit 1
git pull git pull
git add . git add .
current_date=$(date +"%Y-%m-%d %T") 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 git push
} }

View File

@@ -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;
}
}

View File

@@ -0,0 +1,7 @@
[mcu]
serial: /dev/serial/by-id/<your-mcu-id>
[printer]
kinematics: none
max_velocity: 1000
max_accel: 1000

View File

@@ -1,10 +1,10 @@
# /etc/nginx/sites-available/<<UI>> # /etc/nginx/sites-available/fluidd
server { server {
listen 80; listen 80;
access_log /var/log/nginx/<<UI>>-access.log; access_log /var/log/nginx/fluidd-access.log;
error_log /var/log/nginx/<<UI>>-error.log; error_log /var/log/nginx/fluidd-error.log;
# disable this section on smaller hardware like a pi zero # disable this section on smaller hardware like a pi zero
gzip on; gzip on;
@@ -16,8 +16,8 @@ server {
gzip_http_version 1.1; gzip_http_version 1.1;
gzip_types text/plain text/css text/xml text/javascript application/javascript application/x-javascript application/json application/xml; 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 # web_path from fluidd static files
root /home/pi/<<UI>>; root /home/pi/fluidd;
index index.html; index index.html;
server_name _; server_name _;
@@ -55,6 +55,7 @@ server {
proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Scheme $scheme; proxy_set_header X-Scheme $scheme;
proxy_read_timeout 600;
} }
location /webcam/ { location /webcam/ {

View File

@@ -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
View File

@@ -0,0 +1 @@
KLIPPER_ARGS="%KLIPPER_DIR%/klippy/klippy.py %CFG% -I %PRINTER% -l %LOG% -a %UDS%"

View File

@@ -1,7 +1,8 @@
#Systemd service file for klipper
[Unit] [Unit]
Description=Starts klipper instance %INST% on startup Description=Klipper 3D Printer Firmware SV1
After=network.target Documentation=https://www.klipper3d.org/
After=network-online.target
Wants=udev.target
[Install] [Install]
WantedBy=multi-user.target WantedBy=multi-user.target
@@ -10,6 +11,8 @@ WantedBy=multi-user.target
Type=simple Type=simple
User=%USER% User=%USER%
RemainAfterExit=yes 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 Restart=always
RestartSec=10 RestartSec=10

96
resources/mainsail Normal file
View 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/;
}
}

View 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

View 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

View File

@@ -0,0 +1 @@
TELEGRAM_BOT_ARGS="%TELEGRAM_BOT_DIR%/bot/main.py -c %CFG% -l %LOG%"

View 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

View File

@@ -1,12 +1,8 @@
[server] [server]
host: 0.0.0.0 host: 0.0.0.0
port: %PORT% port: %PORT%
enable_debug_logging: False
klippy_uds_address: %UDS% klippy_uds_address: %UDS%
[database]
database_path: %MR_DB%
[authorization] [authorization]
trusted_clients: trusted_clients:
%LAN% %LAN%
@@ -18,16 +14,12 @@ trusted_clients:
FE80::/10 FE80::/10
::1/128 ::1/128
cors_domains: cors_domains:
http://*.lan *.lan
http://*.local *.local
https://my.mainsail.xyz *://localhost
http://my.mainsail.xyz *://localhost:*
https://app.fluidd.xyz *://my.mainsail.xyz
http://app.fluidd.xyz *://app.fluidd.xyz
[file_manager]
config_path: %CFG%
log_path: %LOG%
[octoprint_compat] [octoprint_compat]
@@ -36,21 +28,3 @@ log_path: %LOG%
[update_manager] [update_manager]
channel: dev channel: dev
refresh_interval: 168 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
View File

@@ -0,0 +1 @@
MOONRAKER_ARGS="%MOONRAKER_DIR%/moonraker/moonraker.py -d %PRINTER_DATA%"

View File

@@ -1,16 +1,19 @@
#Systemd service file for moonraker
[Unit] [Unit]
Description=Starts Moonraker %INST% on startup Description=API Server for Klipper SV1 %INST%
After=network.target Documentation=https://moonraker.readthedocs.io/
Requires=network-online.target
After=network-online.target
[Install] [Install]
WantedBy=multi-user.target WantedBy=multi-user.target
[Service] [Service]
Type=simple Type=simple
SupplementaryGroups=moonraker-admin
User=%USER% User=%USER%
SupplementaryGroups=moonraker-admin
RemainAfterExit=yes 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 Restart=always
RestartSec=10 RestartSec=10

View File

@@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

View File

@@ -1,195 +1,193 @@
check_for_backup_dir(){ #!/usr/bin/env bash
if [ ! -d $BACKUP_DIR ]; then
status_msg "Create KIAUH backup directory ..." #=======================================================================#
mkdir -p $BACKUP_DIR && ok_msg "Directory created!" # Copyright (C) 2020 - 2023 Dominik Willner <th33xitus@gmail.com> #
fi # #
# 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}"
} }
toggle_backups(){ function check_for_backup_dir() {
source_kiauh_ini [[ -d ${BACKUP_DIR} ]] && return
if [ "$backup_before_update" = "true" ]; then
sed -i '/backup_before_update=/s/true/false/' $INI_FILE status_msg "Create KIAUH backup directory ..."
BB4U_STATUS="${green}[Enable]${default} backups before updating " mkdir -p "${BACKUP_DIR}" && ok_msg "Directory created!"
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
} }
bb4u(){ function backup_before_update() {
source_kiauh_ini read_kiauh_ini "${FUNCNAME[0]}"
if [ "$backup_before_update" = "true" ]; then local state="${backup_before_update}"
backup_$1 [[ ${state} = "false" ]] && return
fi backup_"${1}"
} }
read_bb4u_stat(){ function backup_config_dir() {
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(){
check_for_backup_dir check_for_backup_dir
if [ -f $PRINTER_CFG ]; then local current_date instance_names config_pathes
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
}
backup_klipper_config_dir(){ config_pathes=$(get_config_folders)
source_kiauh_ini readarray -t -d" " instance_names < <(get_multi_instance_names)
check_for_backup_dir
if [ -d "$klipper_cfg_loc" ]; then if [[ -n "${config_pathes}" ]]; then
get_date current_date=$(get_date)
status_msg "Timestamp: $current_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)" local i=0 folder
mkdir -p $BACKUP_DIR/$config_folder_name/$current_date for folder in ${config_pathes}; do
cp -r "$klipper_cfg_loc" "$_" && ok_msg "Backup complete!" local folder_name="${instance_names[${i}]}"
echo 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 else
ok_msg "No config directory found! Skipping backup ..." ok_msg "No config directory found! Skipping backup ..."
echo
fi fi
} }
backup_moonraker_database(){ function backup_moonraker_database() {
check_for_backup_dir check_for_backup_dir
if ls -d ${HOME}/.moonraker_database* 2>/dev/null 1>&2; then local current_date db_pathes
get_date
status_msg "Timestamp: $current_date" db_pathes=$(get_instance_folder_path "database")
mkdir -p "$BACKUP_DIR/mr_db_backup/$current_date" readarray -t -d" " instance_names < <(get_multi_instance_names)
for database in $(ls -d ${HOME}/.moonraker_database*)
do if [[ -n ${db_pathes} ]]; then
status_msg "Create backup of $database ..." current_date=$(get_date)
cp -r $database "$BACKUP_DIR/mr_db_backup/$current_date" status_msg "Timestamp: ${current_date}"
ok_msg "Done!"
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 done
ok_msg "Backup complete!\n"
else else
ok_msg "No Moonraker database found! Skipping backup ..." print_error "No Moonraker database found! Skipping backup ..."
fi fi
} }
backup_klipper(){ function backup_klipper() {
if [ -d $KLIPPER_DIR ] && [ -d $KLIPPY_ENV ]; then local current_date
if [[ -d ${KLIPPER_DIR} && -d ${KLIPPY_ENV} ]]; then
status_msg "Creating Klipper backup ..." status_msg "Creating Klipper backup ..."
check_for_backup_dir check_for_backup_dir
get_date current_date=$(get_date)
status_msg "Timestamp: $current_date" status_msg "Timestamp: ${current_date}"
mkdir -p $BACKUP_DIR/klipper-backups/"$current_date" mkdir -p "${BACKUP_DIR}/klipper-backups/${current_date}"
cp -r $KLIPPER_DIR $_ && cp -r $KLIPPY_ENV $_ && ok_msg "Backup complete!" cp -r "${KLIPPER_DIR}" "${_}" && cp -r "${KLIPPY_ENV}" "${_}"
print_confirm "Klipper backup complete!"
else 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 fi
} }
backup_dwc2(){ function backup_mainsail() {
if [ -d $DWC2FK_DIR ] && [ -d $DWC_ENV_DIR ] && [ -d $DWC2_DIR ]; then local current_date
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
}
backup_mainsail(){ if [[ -d ${MAINSAIL_DIR} ]]; then
if [ -d $MAINSAIL_DIR ]; then
status_msg "Creating Mainsail backup ..." status_msg "Creating Mainsail backup ..."
check_for_backup_dir check_for_backup_dir
get_date current_date=$(get_date)
status_msg "Timestamp: $current_date" status_msg "Timestamp: ${current_date}"
mkdir -p $BACKUP_DIR/mainsail-backups/"$current_date" mkdir -p "${BACKUP_DIR}/mainsail-backups/${current_date}"
cp -r $MAINSAIL_DIR $_ && ok_msg "Backup complete!" cp -r "${MAINSAIL_DIR}" "${_}"
print_confirm "Mainsail backup complete!"
else else
ERROR_MSG=" Can't backup mainsail directory! Not found!" print_error "Can't back up 'mainsail' directory! Not found!"
fi fi
} }
backup_fluidd(){ function backup_fluidd() {
if [ -d $FLUIDD_DIR ]; then local current_date
if [[ -d ${FLUIDD_DIR} ]]; then
status_msg "Creating Fluidd backup ..." status_msg "Creating Fluidd backup ..."
check_for_backup_dir check_for_backup_dir
get_date current_date=$(get_date)
status_msg "Timestamp: $current_date" status_msg "Timestamp: ${current_date}"
mkdir -p $BACKUP_DIR/fluidd-backups/"$current_date" mkdir -p "${BACKUP_DIR}/fluidd-backups/${current_date}"
cp -r $FLUIDD_DIR $_ && ok_msg "Backup complete!" cp -r "${FLUIDD_DIR}" "${_}"
print_confirm "Fluidd backup complete!"
else else
ERROR_MSG=" Can't backup fluidd directory! Not found!" print_error "Can't back up 'fluidd' directory! Not found!"
fi fi
} }
backup_moonraker(){ function backup_moonraker() {
if [ -d $MOONRAKER_DIR ] && [ -d $MOONRAKER_ENV ]; then local current_date
if [[ -d ${MOONRAKER_DIR} && -d ${MOONRAKER_ENV} ]]; then
status_msg "Creating Moonraker backup ..." status_msg "Creating Moonraker backup ..."
check_for_backup_dir check_for_backup_dir
get_date current_date=$(get_date)
status_msg "Timestamp: $current_date" status_msg "Timestamp: ${current_date}"
mkdir -p $BACKUP_DIR/moonraker-backups/"$current_date" mkdir -p "${BACKUP_DIR}/moonraker-backups/${current_date}"
cp -r $MOONRAKER_DIR $_ && cp -r $MOONRAKER_ENV $_ && ok_msg "Backup complete!" cp -r "${MOONRAKER_DIR}" "${_}" && cp -r "${MOONRAKER_ENV}" "${_}"
print_confirm "Moonraker backup complete!"
else 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 fi
} }
backup_octoprint(){ function backup_octoprint() {
if [ -d $OCTOPRINT_DIR ] && [ -d $OCTOPRINT_CFG_DIR ]; then local current_date
if [[ -d ${OCTOPRINT_DIR} && -d ${OCTOPRINT_CFG_DIR} ]]; then
status_msg "Creating OctoPrint backup ..." status_msg "Creating OctoPrint backup ..."
check_for_backup_dir check_for_backup_dir
get_date current_date=$(get_date)
status_msg "Timestamp: $current_date" status_msg "Timestamp: ${current_date}"
mkdir -p $BACKUP_DIR/octoprint-backups/"$current_date" mkdir -p "${BACKUP_DIR}/octoprint-backups/${current_date}"
cp -r $OCTOPRINT_DIR $_ && cp -r $OCTOPRINT_CFG_DIR $_ cp -r "${OCTOPRINT_DIR}" "${_}" && cp -r "${OCTOPRINT_CFG_DIR}" "${_}"
ok_msg "Backup complete!" print_confirm " OctoPrint backup complete!"
else 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 fi
} }
backup_klipperscreen(){ function backup_klipperscreen() {
if [ -d $KLIPPERSCREEN_DIR ] ; then local current_date
if [[ -d ${KLIPPERSCREEN_DIR} ]] ; then
status_msg "Creating KlipperScreen backup ..." status_msg "Creating KlipperScreen backup ..."
check_for_backup_dir check_for_backup_dir
get_date current_date=$(get_date)
status_msg "Timestamp: $current_date" status_msg "Timestamp: ${current_date}"
mkdir -p $BACKUP_DIR/klipperscreen-backups/"$current_date" mkdir -p "${BACKUP_DIR}/klipperscreen-backups/${current_date}"
cp -r $KLIPPERSCREEN_DIR $_ cp -r "${KLIPPERSCREEN_DIR}" "${_}"
ok_msg "Backup complete!" print_confirm "KlipperScreen backup complete!"
else else
ERROR_MSG=" Can't backup KlipperScreen directory!\n Not found!" print_error "Can't back up KlipperScreen directory!\n Not found!"
fi fi
} }
backup_MoonrakerTelegramBot(){ function backup_telegram_bot() {
if [ -d $MOONRAKER_TELEGRAM_BOT_DIR ] ; then local current_date
if [[ -d ${TELEGRAM_BOT_DIR} ]] ; then
status_msg "Creating MoonrakerTelegramBot backup ..." status_msg "Creating MoonrakerTelegramBot backup ..."
check_for_backup_dir check_for_backup_dir
get_date current_date=$(get_date)
status_msg "Timestamp: $current_date" status_msg "Timestamp: ${current_date}"
mkdir -p $BACKUP_DIR/MoonrakerTelegramBot-backups/"$current_date" mkdir -p "${BACKUP_DIR}/MoonrakerTelegramBot-backups/${current_date}"
cp -r $MOONRAKER_TELEGRAM_BOT_DIR $_ cp -r "${TELEGRAM_BOT_DIR}" "${_}"
ok_msg "Backup complete!" print_confirm "MoonrakerTelegramBot backup complete!"
else else
ERROR_MSG=" Can't backup MoonrakerTelegramBot directory!\n Not found!" print_error "Can't back up MoonrakerTelegramBot directory!\n Not found!"
fi fi
} }

235
scripts/crowsnest.sh Normal file
View 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"
}

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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
View 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
}

View File

@@ -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
View 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
}

View File

@@ -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
}

View 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
View 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