Compare commits

...

136 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
112 changed files with 8570 additions and 1468 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

8
.gitignore vendored
View File

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

169
README.md
View File

@@ -1,6 +1,6 @@
<p align="center"> <p align="center">
<a> <a>
<img src="https://raw.githubusercontent.com/th33xitus/kiauh/master/resources/screenshots/kiauh.png" alt="KIAUH logo" height="181"> <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> <h1 align="center">Klipper Installation And Update Helper</h1>
</a> </a>
</p> </p>
@@ -10,84 +10,181 @@
</p> </p>
<p align="center"> <p align="center">
<a><img src="https://img.shields.io/github/license/th33xitus/kiauh"></a> <a><img src="https://img.shields.io/github/license/dw-0/kiauh"></a>
<a><img src="https://img.shields.io/github/stars/th33xitus/kiauh"></a> <a><img src="https://img.shields.io/github/stars/dw-0/kiauh"></a>
<a><img src="https://img.shields.io/github/forks/th33xitus/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/th33xitus/kiauh?logo=gnubash&logoColor=white"></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/th33xitus/kiauh"></a> <a><img src="https://img.shields.io/github/v/tag/dw-0/kiauh"></a>
<br /> <br />
<a><img src="https://img.shields.io/github/last-commit/th33xitus/kiauh"></a> <a><img src="https://img.shields.io/github/last-commit/dw-0/kiauh"></a>
<a><img src="https://img.shields.io/github/contributors/th33xitus/kiauh"></a> <a><img src="https://img.shields.io/github/contributors/dw-0/kiauh"></a>
</p> </p>
## **🛠️ Instructions:** <hr>
For downloading this script it is necessary to have git installed.\ <h2 align="center">
If you haven't, please run `sudo apt-get install git -y` to install git first.\ 📄️ Instructions 📄
After git is installed, use the following commands in the given order to download and execute the script: </h2>
```shell ### 📋 Prerequisites
cd ~ 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.
git clone https://github.com/th33xitus/kiauh.git * 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>
./kiauh/kiauh.sh * 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
```
## **❗ Notes:** * **Step 2:** \
Once git is installed, use the following command to download KIAUH into your home-directory:
**📋 Please see the [Changelog](docs/changelog.md) for possible important changes!** ```shell
cd ~ && git clone https://github.com/dw-0/kiauh.git
```
- Tested **only** on Raspberry Pi OS Lite (Debian 10 Buster) * **Step 3:** \
- Other Debian based distributions can work Finally, start KIAUH by running the next command:
- Reported to work on Armbian too
- During the use of this script you might be asked for your sudo password. There are several functions involved which need sudo privileges.
## **🌐 Sources & Further Information** ```shell
./kiauh/kiauh.sh
```
* **Step 4:** \
You should now find yourself in the main menu of KIAUH. You will see several actions to choose from depending
on what you want to do. To choose an action, simply type the corresponding number into the "Perform action"
prompt and confirm by hitting ENTER.
<hr>
<h2 align="center">❗ Notes ❗</h2>
### **📋 Please see the [Changelog](docs/changelog.md) for possible important changes!**
- 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.
<hr>
<h2 align="center">🌐 Sources & Further Information</h2>
<table> <table>
<tr> <tr>
<th><h3><a href="https://github.com/Klipper3d/klipper">Klipper</a></h3></th> <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/Arksine/moonraker">Moonraker</a></h3></th>
<th><h3><a href="https://github.com/mainsail-crew/mainsail">Mainsail</a></h3></th> <th><h3><a href="https://github.com/mainsail-crew/mainsail">Mainsail</a></h3></th>
<th><h3><a href="https://github.com/fluidd-core/fluidd">Fluidd</a></h3></th>
</tr> </tr>
<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://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://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> <th><img src="https://raw.githubusercontent.com/mainsail-crew/docs/master/assets/img/logo.png" alt="Mainsail Logo" height="64"></th>
<th><img src="https://raw.githubusercontent.com/fluidd-core/fluidd/master/docs/assets/images/logo.svg" alt="Fluidd Logo" height="64"></th>
</tr> </tr>
<tr> <tr>
<th>by <a href="https://github.com/KevinOConnor">KevinOConnor</a></th> <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/Arksine">Arksine</a></th>
<th>by <a href="https://github.com/mainsail-crew">mainsail-crew</a></th> <th>by <a href="https://github.com/mainsail-crew">mainsail-crew</a></th>
<th>by <a href="https://github.com/fluidd-core">fluidd-core</a></th>
</tr> </tr>
<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/jordanruthe/KlipperScreen">KlipperScreen</a></h3></th>
<th><h3><a href="https://github.com/nlef/moonraker-telegram-bot">Moonraker-Telegram-Bot</a></h3></th>
<th><h3><a href="https://github.com/Kragrathea/pgcode">PrettyGCode for Klipper</a></h3></th>
<th><h3><a href="https://github.com/OctoPrint/OctoPrint">OctoPrint</a></h3></th> <th><h3><a href="https://github.com/OctoPrint/OctoPrint">OctoPrint</a></h3></th>
</tr> </tr>
<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://avatars.githubusercontent.com/u/31575189?v=4" alt="jordanruthe avatar" height="64"></th>
<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://camo.githubusercontent.com/627be7fc67195b626b298af9b9677d7c58e698c67305e54324cffbe06130d4a4/68747470733a2f2f6f63746f7072696e742e6f72672f6173736574732f696d672f6c6f676f2e706e67" alt="OctoPrint Logo" height="64"></th> <th><img src="https://camo.githubusercontent.com/627be7fc67195b626b298af9b9677d7c58e698c67305e54324cffbe06130d4a4/68747470733a2f2f6f63746f7072696e742e6f72672f6173736574732f696d672f6c6f676f2e706e67" alt="OctoPrint Logo" height="64"></th>
</tr> </tr>
<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/jordanruthe">jordanruthe</a></th>
<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/OctoPrint">OctoPrint</a></th> <th>by <a href="https://github.com/OctoPrint">OctoPrint</a></th>
</tr> </tr>
<tr>
<th><h3><a href="https://github.com/nlef/moonraker-telegram-bot">Moonraker-Telegram-Bot</a></h3></th>
<th><h3><a href="https://github.com/Kragrathea/pgcode">PrettyGCode for Klipper</a></h3></th>
<th><h3><a href="https://github.com/TheSpaghettiDetective/moonraker-obico">Obico for Klipper</a></h3></th>
</tr>
<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>
<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>
<tr>
<th><h3><a href="https://github.com/Clon1998/mobileraker_companion">Mobileraker's Companion</a></h3></th>
<th><h3><a href="https://octoeverywhere.com/?source=kiauh_readme">OctoEverywhere For Klipper</a></h3></th>
<th><h3></h3></th>
</tr>
<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> </table>
## **Credits** <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,20 +2,95 @@
This document covers possible important changes to KIAUH. This document covers possible important changes to KIAUH.
### 2022-04-XX ### 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 ! KIAUH has now reached major version 4 !
* feat: Klipper can be installed under Python3 (considered as experimental) * feat: Klipper can be installed under Python3 (still considered as experimental)
* feat: Klipper can be installed from custom repositories / inofficial forks * feat: Klipper can be installed from custom repositories / inofficial forks
* feat: Custom instance name for multi instance installations of Klipper * 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 * 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 * E.g. klipper-voron2 -> moonraker-voron2 -> moonraker-telegram-bot-voron2
* feat: Option to only allow the installation of stable Mainsail and Fluidd versions * 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 * feat: Multi-Instance OctoPrint installations now each have their own virtual python environment
* allows independent installation of plugins for each instance * allows independent installation of plugins for each instance
* feat: Implementing the use of shellcheck during development * feat: Implementing the use of shellcheck during development
* feat: Implementing a simple logging mechanic * feat: Implementing a simple logging mechanic
* feat: Log-upload function now also allows uploading other logfiles (kiauh.log, webcamd.log etc.) * 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 * 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: Support for DWC and DWC-for-Klipper has been removed
* refactor: The backup before update settings were moved to the KIAUH settings menu * 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: Switch branch function has been removed (was replaced by the custom Klipper repo feature)

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

163
kiauh.sh
View File

@@ -1,10 +1,10 @@
#!/usr/bin/env bash #!/usr/bin/env bash
#=======================================================================# #=======================================================================#
# Copyright (C) 2020 - 2022 Dominik Willner <th33xitus@gmail.com> # # Copyright (C) 2020 - 2023 Dominik Willner <th33xitus@gmail.com> #
# # # #
# This file is part of KIAUH - Klipper Installation And Update Helper # # This file is part of KIAUH - Klipper Installation And Update Helper #
# https://github.com/th33xitus/kiauh # # https://github.com/dw-0/kiauh #
# # # #
# This file may be distributed under the terms of the GNU GPLv3 license # # This file may be distributed under the terms of the GNU GPLv3 license #
#=======================================================================# #=======================================================================#
@@ -12,78 +12,97 @@
set -e set -e
clear clear
### sourcing all additional scripts function main() {
KIAUH_SRCDIR="$(dirname -- "$(readlink -f "${BASH_SOURCE[0]}")")" local python_command
for script in "${KIAUH_SRCDIR}/scripts/"*.sh; do . "${script}"; done local entrypoint
for script in "${KIAUH_SRCDIR}/scripts/ui/"*.sh; do . "${script}"; done
#===================================================# if command -v python3 &>/dev/null; then
#=================== UPDATE KIAUH ==================# python_command="python3"
#===================================================# elif command -v python &>/dev/null; then
python_command="python"
function update_kiauh() { else
status_msg "Updating KIAUH ..." echo "Python is not installed. Please install Python and try again."
exit 1
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 fi
entrypoint=$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")
${python_command} "${entrypoint}/kiauh.py"
} }
function kiauh_update_dialog() { main
[[ ! $(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 #### sourcing all additional scripts
read -p "${cyan}###### Do you want to update now? (Y/n):${white} " yn #KIAUH_SRCDIR="$(dirname -- "$(readlink -f "${BASH_SOURCE[0]}")")"
while true; do #for script in "${KIAUH_SRCDIR}/scripts/"*.sh; do . "${script}"; done
case "${yn}" in #for script in "${KIAUH_SRCDIR}/scripts/ui/"*.sh; do . "${script}"; done
Y|y|Yes|yes|"") #
do_action "update_kiauh" ##===================================================#
break;; ##=================== UPDATE KIAUH ==================#
N|n|No|no) ##===================================================#
break;; #
*) #function update_kiauh() {
deny_action "kiauh_update_dialog";; # status_msg "Updating KIAUH ..."
esac #
done # cd "${KIAUH_SRCDIR}"
} # git reset --hard && git pull
#
check_euid # ok_msg "Update complete! Please restart KIAUH."
init_logfile # exit 0
set_globals #}
init_ini #
kiauh_update_dialog ##===================================================#
main_menu ##=================== 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

@@ -0,0 +1,11 @@
[mcu]
serial: /dev/serial/by-id/<your-mcu-id>
[virtual_sdcard]
path: %GCODES_DIR%
on_error_gcode: CANCEL_PRINT
[printer]
kinematics: none
max_velocity: 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

View File

@@ -1,18 +0,0 @@
# This file acts as an example file.
#
# 1) Make a copy of this file and rename it to 'klipper_repos.txt'
# 2) Add your custom Klipper repository to the bottom of that copy
# 3) Save the file
#
# Back in KIAUH you can now go into -> [Settings] and use action '2' to set a different Klipper repository
#
# Make sure to always separate the repository and the branch with a ','.
# <repository>,<branch> -> https://github.com/Klipper3d/klipper,master
# If you omit a branch, it will always default to 'master'
#
# You are allowed to omit the 'https://github.com/' part of the repository URL
# Down below are now a few examples of what is considered as valid:
https://github.com/Klipper3d/klipper,master
https://github.com/Klipper3d/klipper
Klipper3d/klipper,master
Klipper3d/klipper

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 @@
# /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,22 +1,18 @@
#Systemd Klipper Service
[Unit] [Unit]
Description=Systemd Klipper Service for instance klipper-%INST% Description=Klipper 3D Printer Firmware SV1
Documentation=https://www.klipper3d.org/ Documentation=https://www.klipper3d.org/
After=network.target After=network-online.target
Wants=udev.target Wants=udev.target
[Install] [Install]
WantedBy=multi-user.target WantedBy=multi-user.target
[Service] [Service]
Environment=KLIPPER_CONFIG=%CFG%
Environment=KLIPPER_LOG=%LOG%
Environment=KLIPPER_SOCKET=%UDS%
Environment=KLIPPER_PRINTER=%PRINTER%
Type=simple Type=simple
User=%USER% User=%USER%
RemainAfterExit=yes RemainAfterExit=yes
ExecStart=%ENV%/bin/python %DIR%/klippy/klippy.py ${KLIPPER_CONFIG} -I ${KLIPPER_PRINTER} -l ${KLIPPER_LOG} -a ${KLIPPER_SOCKET} 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

@@ -1,19 +1,16 @@
#Systemd service file for Moonraker Telegram Bot
[Unit] [Unit]
Description=Starts Moonraker Telegram Bot instance %INST% on startup Description=Moonraker Telegram Bot SV1 %INST%
Documentation=https://github.com/nlef/moonraker-telegram-bot/wiki Documentation=https://github.com/nlef/moonraker-telegram-bot/wiki
After=network.target After=network-online.target
[Install] [Install]
WantedBy=multi-user.target WantedBy=multi-user.target
[Service] [Service]
Environment=TELEGRAM_CONF=%CFG%
Environment=TELEGRAM_LOG=%LOG%
Type=simple Type=simple
User=%USER% User=%USER%
RemainAfterExit=yes WorkingDirectory=%TELEGRAM_BOT_DIR%
ExecStart=%ENV%/bin/python %DIR%/bot/main.py -c ${TELEGRAM_CONF} -l ${TELEGRAM_LOG} EnvironmentFile=%ENV_FILE%
ExecStart=%ENV%/bin/python $TELEGRAM_BOT_ARGS
Restart=always Restart=always
RestartSec=10 RestartSec=10

View File

@@ -1,7 +1,6 @@
[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%
[authorization] [authorization]
@@ -15,19 +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
[database]
database_path: %DB%
[file_manager]
config_path: %CFG%
log_path: %LOG%
[octoprint_compat] [octoprint_compat]

1
resources/moonraker.env Normal file
View File

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

View File

@@ -1,20 +1,19 @@
#Systemd Moonraker Service
[Unit] [Unit]
Description=Systemd Moonraker Service for instance moonraker-%INST% Description=API Server for Klipper SV1 %INST%
Documentation=https://moonraker.readthedocs.io/ Documentation=https://moonraker.readthedocs.io/
After=network.target Requires=network-online.target
After=network-online.target
[Install] [Install]
WantedBy=multi-user.target WantedBy=multi-user.target
[Service] [Service]
Environment=MOONRAKER_CONF=%CFG%
Environment=MOONRAKER_LOG=%LOG%
Type=simple Type=simple
SupplementaryGroups=moonraker-admin
User=%USER% User=%USER%
SupplementaryGroups=moonraker-admin
RemainAfterExit=yes RemainAfterExit=yes
ExecStart=%ENV%/bin/python %DIR%/moonraker/moonraker.py -c ${MOONRAKER_CONF} -l ${MOONRAKER_LOG} WorkingDirectory=%MOONRAKER_DIR%
EnvironmentFile=%ENV_FILE%
ExecStart=%ENV%/bin/python $MOONRAKER_ARGS
Restart=always Restart=always
RestartSec=10 RestartSec=10

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

View File

@@ -1,10 +1,10 @@
#!/usr/bin/env bash #!/usr/bin/env bash
#=======================================================================# #=======================================================================#
# Copyright (C) 2020 - 2022 Dominik Willner <th33xitus@gmail.com> # # Copyright (C) 2020 - 2023 Dominik Willner <th33xitus@gmail.com> #
# # # #
# This file is part of KIAUH - Klipper Installation And Update Helper # # This file is part of KIAUH - Klipper Installation And Update Helper #
# https://github.com/th33xitus/kiauh # # https://github.com/dw-0/kiauh #
# # # #
# This file may be distributed under the terms of the GNU GPLv3 license # # This file may be distributed under the terms of the GNU GPLv3 license #
#=======================================================================# #=======================================================================#
@@ -31,21 +31,26 @@ function backup_before_update() {
backup_"${1}" backup_"${1}"
} }
function backup_klipper_config_dir() { function backup_config_dir() {
check_for_backup_dir check_for_backup_dir
local current_date config_folder_name local current_date instance_names config_pathes
if [[ -d "${KLIPPER_CONFIG}" ]]; then config_pathes=$(get_config_folders)
readarray -t -d" " instance_names < <(get_multi_instance_names)
if [[ -n "${config_pathes}" ]]; then
current_date=$(get_date) current_date=$(get_date)
config_folder_name="$(echo "${KLIPPER_CONFIG}" | rev | cut -d"/" -f1 | rev)"
status_msg "Timestamp: ${current_date}" status_msg "Timestamp: ${current_date}"
status_msg "Create backup of the Klipper config directory ..."
mkdir -p "${BACKUP_DIR}/${config_folder_name}/${current_date}" local i=0 folder
cp -r "${KLIPPER_CONFIG}" "${_}" for folder in ${config_pathes}; do
local folder_name="${instance_names[${i}]}"
print_confirm "Configuration directory backup complete!" 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 ..."
fi fi
@@ -53,27 +58,27 @@ function backup_klipper_config_dir() {
function backup_moonraker_database() { function backup_moonraker_database() {
check_for_backup_dir check_for_backup_dir
local current_date databases target_dir regex=".moonraker_database(_[0-9a-zA-Z]+)?" local current_date db_pathes
databases=$(find "${HOME}" -maxdepth 1 -type d -regextype posix-extended -regex "${HOME}/${regex}" | sort)
if [[ -n ${databases} ]]; then db_pathes=$(get_instance_folder_path "database")
readarray -t -d" " instance_names < <(get_multi_instance_names)
if [[ -n ${db_pathes} ]]; then
current_date=$(get_date) current_date=$(get_date)
target_dir="${BACKUP_DIR}/moonraker_database_backup/${current_date}"
status_msg "Timestamp: ${current_date}" status_msg "Timestamp: ${current_date}"
mkdir -p "${target_dir}"
for database in ${databases}; do local i=0 database
for database in ${db_pathes}; do
local folder_name="${instance_names[${i}]}"
status_msg "Create backup of ${database} ..." status_msg "Create backup of ${database} ..."
cp -r "${database}" "${target_dir}" mkdir -p "${BACKUP_DIR}/moonraker_databases/${current_date}/${folder_name}"
ok_msg "Done!" cp -r "${database}" "${_}"
ok_msg "Backup created in:\n${BACKUP_DIR}/moonraker_databases/${current_date}/${folder_name}"
i=$(( i + 1 ))
done done
print_confirm "Moonraker database backup complete!"
else else
print_error "No Moonraker database found! Skipping backup ..." print_error "No Moonraker database found! Skipping backup ..."
fi fi
return
} }
function backup_klipper() { function backup_klipper() {

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,10 +1,10 @@
#!/usr/bin/env bash #!/usr/bin/env bash
#=======================================================================# #=======================================================================#
# Copyright (C) 2020 - 2022 Dominik Willner <th33xitus@gmail.com> # # Copyright (C) 2020 - 2023 Dominik Willner <th33xitus@gmail.com> #
# # # #
# This file is part of KIAUH - Klipper Installation And Update Helper # # This file is part of KIAUH - Klipper Installation And Update Helper #
# https://github.com/th33xitus/kiauh # # https://github.com/dw-0/kiauh #
# # # #
# This file may be distributed under the terms of the GNU GPLv3 license # # This file may be distributed under the terms of the GNU GPLv3 license #
#=======================================================================# #=======================================================================#
@@ -70,6 +70,7 @@ function select_mcu_connection() {
echo -e "| How is the controller board connected to the host? |" echo -e "| How is the controller board connected to the host? |"
echo -e "| 1) USB |" echo -e "| 1) USB |"
echo -e "| 2) UART |" echo -e "| 2) UART |"
echo -e "| 3) USB (DFU mode) |"
blank_line blank_line
back_help_footer back_help_footer
@@ -85,6 +86,10 @@ function select_mcu_connection() {
status_msg "Identifying MCU possibly connected via UART ...\n" status_msg "Identifying MCU possibly connected via UART ...\n"
get_uart_id || true # continue even after exit code 1 get_uart_id || true # continue even after exit code 1
break;; 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) B|b)
advanced_menu advanced_menu
break;; break;;
@@ -102,7 +107,7 @@ function print_detected_mcu_to_screen() {
local i=1 local i=1
if (( ${#mcu_list[@]} < 1 )); then if (( ${#mcu_list[@]} < 1 )); then
print_error "No MCU found!\n MCU eihter not connected or not detected!" print_error "No MCU found!\n MCU either not connected or not detected!"
return return
fi fi
@@ -121,7 +126,7 @@ function select_mcu_id() {
local i=0 sel_index=0 method=${1} local i=0 sel_index=0 method=${1}
if (( ${#mcu_list[@]} < 1 )); then if (( ${#mcu_list[@]} < 1 )); then
print_error "No MCU found!\n MCU eihter not connected or not detected!" print_error "No MCU found!\n MCU either not connected or not detected!"
return return
fi fi
@@ -329,6 +334,16 @@ function get_uart_id() {
done 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() { function show_flash_method_help() {
top_border top_border
echo -e "| ~~~~~~~~ < ? > Help: Flash MCU < ? > ~~~~~~~~ |" echo -e "| ~~~~~~~~ < ? > Help: Flash MCU < ? > ~~~~~~~~ |"
@@ -406,4 +421,4 @@ function show_mcu_connection_help() {
error_msg "Invalid command!";; error_msg "Invalid command!";;
esac esac
done done
} }

View File

@@ -1,10 +1,10 @@
#!/usr/bin/env bash #!/usr/bin/env bash
#=======================================================================# #=======================================================================#
# Copyright (C) 2020 - 2022 Dominik Willner <th33xitus@gmail.com> # # Copyright (C) 2020 - 2023 Dominik Willner <th33xitus@gmail.com> #
# # # #
# This file is part of KIAUH - Klipper Installation And Update Helper # # This file is part of KIAUH - Klipper Installation And Update Helper #
# https://github.com/th33xitus/kiauh # # https://github.com/dw-0/kiauh #
# # # #
# This file may be distributed under the terms of the GNU GPLv3 license # # This file may be distributed under the terms of the GNU GPLv3 license #
#=======================================================================# #=======================================================================#
@@ -16,10 +16,24 @@ set -e
#===================================================# #===================================================#
function install_fluidd() { function install_fluidd() {
### exit early if moonraker not found
if [[ -z $(moonraker_systemd) ]]; then if [[ -z $(moonraker_systemd) ]]; then
local error="Moonraker not installed! Please install Moonraker first!" local error="Moonraker not installed! It's recommended to install Moonraker first!"
print_error "${error}" && return 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 fi
### checking dependencies ### checking dependencies
@@ -30,7 +44,7 @@ function install_fluidd() {
status_msg "Initializing Fluidd installation ..." status_msg "Initializing Fluidd installation ..."
### first, we create a backup of the full klipper_config dir - safety first! ### first, we create a backup of the full klipper_config dir - safety first!
backup_klipper_config_dir backup_config_dir
### check for other enabled web interfaces ### check for other enabled web interfaces
unset SET_LISTEN_PORT unset SET_LISTEN_PORT
@@ -39,30 +53,6 @@ function install_fluidd() {
### check if another site already listens to port 80 ### check if another site already listens to port 80
fluidd_port_check fluidd_port_check
### ask user to install mjpg-streamer
local install_mjpg_streamer
if [[ ! -f "${SYSTEMD}/webcamd.service" ]]; then
while true; do
echo
top_border
echo -e "| Install MJGP-Streamer for webcam support? |"
bottom_border
read -p "${cyan}###### Please select (y/N):${white} " yn
case "${yn}" in
Y|y|Yes|yes)
select_msg "Yes"
install_mjpg_streamer="true"
break;;
N|n|No|no|"")
select_msg "No"
install_mjpg_streamer="false"
break;;
*)
error_msg "Invalid command!";;
esac
done
fi
### download fluidd ### download fluidd
download_fluidd download_fluidd
@@ -82,9 +72,6 @@ function install_fluidd() {
### add fluidd to the update manager in moonraker.conf ### add fluidd to the update manager in moonraker.conf
patch_fluidd_update_manager patch_fluidd_update_manager
### install mjpg-streamer
[[ ${install_mjpg_streamer} == "true" ]] && install_mjpg-streamer
fetch_webui_ports #WIP fetch_webui_ports #WIP
### confirm message ### confirm message
@@ -92,22 +79,21 @@ function install_fluidd() {
} }
function install_fluidd_macros() { function install_fluidd_macros() {
local yn
while true; do while true; do
echo echo
top_border top_border
echo -e "| It is recommended to have some important macros in |" echo -e "| It is recommended to use special macros in order to |"
echo -e "| your printer configuration to have Fluidd fully |" echo -e "| have Fluidd fully functional and working. |"
echo -e "| functional and working. |"
blank_line blank_line
echo -e "| The recommended macros for Fluidd can be found here: |" echo -e "| The recommended macros for Fluidd can be found here: |"
echo -e "| https://docs.fluidd.xyz/configuration/initial_setup |" echo -e "| https://github.com/fluidd-core/fluidd-config |"
blank_line blank_line
echo -e "| If you already have these macros in your config file, |" echo -e "| If you already use these macros skip this step. |"
echo -e "| skip this step and answer with 'no'. |"
echo -e "| Otherwise you should consider to answer with 'yes' to |" echo -e "| Otherwise you should consider to answer with 'yes' to |"
echo -e "| add the recommended example macros to your config. |" echo -e "| download the recommended macros. |"
bottom_border bottom_border
read -p "${cyan}###### Add the recommended macros? (Y/n):${white} " yn read -p "${cyan}###### Download the recommended macros? (Y/n):${white} " yn
case "${yn}" in case "${yn}" in
Y|y|Yes|yes|"") Y|y|Yes|yes|"")
select_msg "Yes" select_msg "Yes"
@@ -124,34 +110,64 @@ function install_fluidd_macros() {
} }
function download_fluidd_macros() { function download_fluidd_macros() {
local fluidd_cfg="https://raw.githubusercontent.com/fluidd-core/FluiddPI/master/src/modules/fluidd/filesystem/home/pi/klipper_config/fluidd.cfg" local ms_cfg_repo path configs regex line gcode_dir
local configs path
configs=$(find "${KLIPPER_CONFIG}" -type f -name "printer.cfg" | sort)
if [[ -n ${configs} ]]; then ms_cfg_repo="https://github.com/fluidd-core/fluidd-config.git"
for config in ${configs}; do regex="${HOME//\//\\/}\/([A-Za-z0-9_]+)\/config\/printer\.cfg"
path=$(echo "${config}" | rev | cut -d"/" -f2- | rev) configs=$(find "${HOME}" -maxdepth 3 -regextype posix-extended -regex "${regex}" | sort)
if [[ ! -f "${path}/fluidd.cfg" ]]; then
status_msg "Downloading fluidd.cfg to ${path} ..."
log_info "downloading fluidd.cfg to: ${path}"
wget "${fluidd_cfg}" -O "${path}/fluidd.cfg"
### replace user 'pi' with current username to prevent issues in cases where the user is not called 'pi' if [[ -z ${configs} ]]; then
log_info "modify fluidd.cfg" print_error "No printer.cfg found! Installation of Macros will be skipped ..."
sed -i "/^path: \/home\/pi\/gcode_files/ s/\/home\/pi/\/home\/${USER}/" "${path}/fluidd.cfg"
### write include to the very first line of the printer.cfg
if ! grep -Eq "^[include fluidd.cfg]$" "${path}/printer.cfg"; then
log_info "modify printer.cfg"
sed -i "1 i [include fluidd.cfg]" "${path}/printer.cfg"
fi
ok_msg "Done!"
fi
done
else
log_error "execution stopped! reason: no printer.cfg found" log_error "execution stopped! reason: no printer.cfg found"
return return
fi 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() { function download_fluidd() {
@@ -189,7 +205,7 @@ function remove_fluidd_dir() {
rm -rf "${FLUIDD_DIR}" && ok_msg "Directory removed!" rm -rf "${FLUIDD_DIR}" && ok_msg "Directory removed!"
} }
function remove_fluidd_config() { function remove_fluidd_nginx_config() {
if [[ -e "/etc/nginx/sites-available/fluidd" ]]; then if [[ -e "/etc/nginx/sites-available/fluidd" ]]; then
status_msg "Removing Fluidd configuration for Nginx ..." status_msg "Removing Fluidd configuration for Nginx ..."
sudo rm "/etc/nginx/sites-available/fluidd" && ok_msg "File removed!" sudo rm "/etc/nginx/sites-available/fluidd" && ok_msg "File removed!"
@@ -214,8 +230,10 @@ function remove_fluidd_logs() {
} }
function remove_fluidd_log_symlinks() { function remove_fluidd_log_symlinks() {
local files local files regex
files=$(find "${KLIPPER_LOGS}" -name "fluidd*" 2> /dev/null | sort)
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 if [[ -n ${files} ]]; then
for file in ${files}; do for file in ${files}; do
@@ -226,11 +244,34 @@ function remove_fluidd_log_symlinks() {
fi 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() { function remove_fluidd() {
remove_fluidd_dir remove_fluidd_dir
remove_fluidd_config remove_fluidd_nginx_config
remove_fluidd_logs remove_fluidd_logs
remove_fluidd_log_symlinks remove_fluidd_log_symlinks
remove_legacy_fluidd_log_symlinks
### remove fluidd_port from ~/.kiauh.ini ### remove fluidd_port from ~/.kiauh.ini
sed -i "/^fluidd_port=/d" "${INI_FILE}" sed -i "/^fluidd_port=/d" "${INI_FILE}"
@@ -276,36 +317,40 @@ function get_fluidd_status() {
} }
function get_local_fluidd_version() { function get_local_fluidd_version() {
[[ ! -f "${FLUIDD_DIR}/.version" ]] && return local versionfile="${FLUIDD_DIR}/.version"
local relinfofile="${FLUIDD_DIR}/release_info.json"
local version local version
version=$(head -n 1 "${FLUIDD_DIR}/.version")
if [[ -f ${relinfofile} ]]; then
version=$(grep -o '"version":"[^"]*' "${relinfofile}" | grep -o '[^"]*$')
elif [[ -f ${versionfile} ]]; then
version=$(head -n 1 "${versionfile}")
fi
echo "${version}" echo "${version}"
} }
function get_remote_fluidd_version() { function get_remote_fluidd_version() {
[[ ! $(dpkg-query -f'${Status}' --show curl 2>/dev/null) = *\ installed ]] && return [[ ! $(dpkg-query -f'${Status}' --show curl 2>/dev/null) = *\ installed ]] && return
local version local tags
version=$(get_fluidd_download_url | rev | cut -d"/" -f2 | rev) tags=$(curl -s "https://api.github.com/repos/fluidd-core/fluidd/tags" | grep "name" | cut -d'"' -f4)
echo "${version}" echo "${tags}" | head -1
} }
function compare_fluidd_versions() { function compare_fluidd_versions() {
unset FLUIDD_UPDATE_AVAIL
local versions local_ver remote_ver local versions local_ver remote_ver
local_ver="$(get_local_fluidd_version)" local_ver="$(get_local_fluidd_version)"
remote_ver="$(get_remote_fluidd_version)" remote_ver="$(get_remote_fluidd_version)"
if [[ ${local_ver} != "${remote_ver}" ]]; then if [[ ${local_ver} != "${remote_ver}" && ${local_ver} != "" ]]; then
versions="${yellow}$(printf " %-14s" "${local_ver}")${white}" versions="${yellow}$(printf " %-14s" "${local_ver}")${white}"
versions+="|${green}$(printf " %-13s" "${remote_ver}")${white}" versions+="|${green}$(printf " %-13s" "${remote_ver}")${white}"
# add fluidd to the update all array for the update all function in the updater # add moonraker to application_updates_available in kiauh.ini
FLUIDD_UPDATE_AVAIL="true" && update_arr+=(update_fluidd) add_to_application_updates "fluidd"
else else
versions="${green}$(printf " %-14s" "${local_ver}")${white}" versions="${green}$(printf " %-14s" "${local_ver}")${white}"
versions+="|${green}$(printf " %-13s" "${remote_ver}")${white}" versions+="|${green}$(printf " %-13s" "${remote_ver}")${white}"
FLUIDD_UPDATE_AVAIL="false"
fi fi
echo "${versions}" echo "${versions}"
@@ -316,25 +361,28 @@ function compare_fluidd_versions() {
#================================================# #================================================#
function get_fluidd_download_url() { function get_fluidd_download_url() {
local tags latest_tag latest_url stable_tag stable_url url local releases_by_tag tags tag unstable_url url
tags=$(curl -s "${FLUIDD_TAGS}" | grep "name" | cut -d'"' -f4)
### latest download url including pre-releases (alpha, beta, rc) ### latest stable download url
latest_tag=$(echo "${tags}" | head -1) url="https://github.com/fluidd-core/fluidd/releases/latest/download/fluidd.zip"
latest_url="https://github.com/fluidd-core/fluidd/releases/download/${latest_tag}/fluidd.zip"
### get stable fluidd download url
stable_tag=$(echo "${tags}" | grep -E "^v([0-9]+\.?){3}$" | head -1)
stable_url="https://github.com/fluidd-core/fluidd/releases/download/${stable_tag}/fluidd.zip"
read_kiauh_ini "${FUNCNAME[0]}" read_kiauh_ini "${FUNCNAME[0]}"
if [[ ${fluidd_install_unstable} == "true" ]]; then if [[ ${fluidd_install_unstable} == "true" ]]; then
url="${latest_url}" releases_by_tag="https://api.github.com/repos/fluidd-core/fluidd/tags"
echo "${url}" tags=$(curl -s "${releases_by_tag}" | grep "name" | cut -d'"' -f4)
else tag=$(echo "${tags}" | head -1)
url="${stable_url}"
echo "${url}" ### 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 fi
echo "${url}"
} }
function fluidd_port_check() { function fluidd_port_check() {
@@ -351,7 +399,7 @@ function fluidd_port_check() {
select_fluidd_port select_fluidd_port
fi fi
else else
DEFAULT_PORT=$(grep listen "${KIAUH_SRCDIR}/resources/klipper_webui_nginx.cfg" | head -1 | sed 's/^\s*//' | cut -d" " -f2 | cut -d";" -f1) DEFAULT_PORT=$(grep listen "${KIAUH_SRCDIR}/resources/fluidd" | head -1 | sed 's/^\s*//' | cut -d" " -f2 | cut -d";" -f1)
SET_LISTEN_PORT=${DEFAULT_PORT} SET_LISTEN_PORT=${DEFAULT_PORT}
fi fi
SET_NGINX_CFG="true" SET_NGINX_CFG="true"
@@ -371,7 +419,7 @@ function select_fluidd_port() {
blank_line blank_line
[[ ${MAINSAIL_PORT} == "80" ]] && echo "| ● Mainsail |" [[ ${MAINSAIL_PORT} == "80" ]] && echo "| ● Mainsail |"
blank_line blank_line
echo -e "| Make sure you don't choose a port which is already |" echo -e "| Make sure you don't choose a port which was already |"
echo -e "| assigned to another webinterface! |" echo -e "| assigned to another webinterface! |"
blank_line blank_line
echo -e "| Be aware: there is ${red}NO${white} sanity check for the following |" echo -e "| Be aware: there is ${red}NO${white} sanity check for the following |"
@@ -397,11 +445,13 @@ function select_fluidd_port() {
} }
function patch_fluidd_update_manager() { function patch_fluidd_update_manager() {
local moonraker_configs local patched moonraker_configs regex
moonraker_configs=$(find "${KLIPPER_CONFIG}" -type f -name "moonraker.conf" | sort) 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 for conf in ${moonraker_configs}; do
if ! grep -Eq "^\[update_manager fluidd\]$" "${conf}"; then if ! grep -Eq "^\[update_manager fluidd\]\s*$" "${conf}"; then
### add new line to conf if it doesn't end with one ### add new line to conf if it doesn't end with one
[[ $(tail -c1 "${conf}" | wc -l) -eq 0 ]] && echo "" >> "${conf}" [[ $(tail -c1 "${conf}" | wc -l) -eq 0 ]] && echo "" >> "${conf}"
@@ -417,5 +467,44 @@ path: ~/fluidd
MOONRAKER_CONF MOONRAKER_CONF
fi fi
patched="true"
done 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,10 +1,10 @@
#!/usr/bin/env bash #!/usr/bin/env bash
#=======================================================================# #=======================================================================#
# Copyright (C) 2020 - 2022 Dominik Willner <th33xitus@gmail.com> # # Copyright (C) 2020 - 2023 Dominik Willner <th33xitus@gmail.com> #
# # # #
# This file is part of KIAUH - Klipper Installation And Update Helper # # This file is part of KIAUH - Klipper Installation And Update Helper #
# https://github.com/th33xitus/kiauh # # https://github.com/dw-0/kiauh #
# # # #
# This file may be distributed under the terms of the GNU GPLv3 license # # This file may be distributed under the terms of the GNU GPLv3 license #
#=======================================================================# #=======================================================================#
@@ -103,12 +103,13 @@ function install_gcode_shell_command() {
function create_example_shell_command() { function create_example_shell_command() {
### create a backup of the config folder ### create a backup of the config folder
backup_klipper_config_dir backup_config_dir
local printer_cfgs path local configs regex path
printer_cfgs=$(find "$(get_klipper_cfg_dir)" -type f -name "printer.cfg" | sort) regex="${HOME//\//\\/}\/([A-Za-z0-9_]+)\/config\/printer\.cfg"
configs=$(find "${HOME}" -maxdepth 3 -regextype posix-extended -regex "${regex}" | sort)
for cfg in ${printer_cfgs}; do for cfg in ${configs}; do
path=$(echo "${cfg}" | rev | cut -d"/" -f2- | rev) path=$(echo "${cfg}" | rev | cut -d"/" -f2- | rev)
if [[ ! -f "${path}/shell_command.cfg" ]]; then if [[ ! -f "${path}/shell_command.cfg" ]]; then
@@ -119,4 +120,4 @@ function create_example_shell_command() {
sed -i "1 i [include shell_command.cfg]" "${cfg}" sed -i "1 i [include shell_command.cfg]" "${cfg}"
fi fi
done done
} }

View File

@@ -1,10 +1,10 @@
#!/usr/bin/env bash #!/usr/bin/env bash
#=======================================================================# #=======================================================================#
# Copyright (C) 2020 - 2022 Dominik Willner <th33xitus@gmail.com> # # Copyright (C) 2020 - 2023 Dominik Willner <th33xitus@gmail.com> #
# # # #
# This file is part of KIAUH - Klipper Installation And Update Helper # # This file is part of KIAUH - Klipper Installation And Update Helper #
# https://github.com/th33xitus/kiauh # # https://github.com/dw-0/kiauh #
# # # #
# This file may be distributed under the terms of the GNU GPLv3 license # # This file may be distributed under the terms of the GNU GPLv3 license #
#=======================================================================# #=======================================================================#
@@ -34,8 +34,6 @@ function set_globals() {
KLIPPY_ENV="${HOME}/klippy-env" KLIPPY_ENV="${HOME}/klippy-env"
KLIPPER_DIR="${HOME}/klipper" KLIPPER_DIR="${HOME}/klipper"
KLIPPER_REPO="https://github.com/Klipper3d/klipper.git" KLIPPER_REPO="https://github.com/Klipper3d/klipper.git"
KLIPPER_LOGS="${HOME}/klipper_logs"
KLIPPER_CONFIG="$(get_klipper_cfg_dir)" # default: ${HOME}/klipper_config
#================= MOONRAKER ==================# #================= MOONRAKER ==================#
MOONRAKER_ENV="${HOME}/moonraker-env" MOONRAKER_ENV="${HOME}/moonraker-env"
@@ -44,13 +42,9 @@ function set_globals() {
#================= MAINSAIL ===================# #================= MAINSAIL ===================#
MAINSAIL_DIR="${HOME}/mainsail" MAINSAIL_DIR="${HOME}/mainsail"
MAINSAIL_REPO_API="https://api.github.com/repos/mainsail-crew/mainsail/releases"
MAINSAIL_TAGS="https://api.github.com/repos/mainsail-crew/mainsail/tags"
#================== FLUIDD ====================# #================== FLUIDD ====================#
FLUIDD_DIR="${HOME}/fluidd" FLUIDD_DIR="${HOME}/fluidd"
FLUIDD_REPO_API="https://api.github.com/repos/fluidd-core/fluidd/releases"
FLUIDD_TAGS="https://api.github.com/repos/fluidd-core/fluidd/tags"
#=============== KLIPPERSCREEN ================# #=============== KLIPPERSCREEN ================#
KLIPPERSCREEN_ENV="${HOME}/.KlipperScreen-env" KLIPPERSCREEN_ENV="${HOME}/.KlipperScreen-env"
@@ -70,4 +64,23 @@ function set_globals() {
NGINX_SA="/etc/nginx/sites-available" NGINX_SA="/etc/nginx/sites-available"
NGINX_SE="/etc/nginx/sites-enabled" NGINX_SE="/etc/nginx/sites-enabled"
NGINX_CONFD="/etc/nginx/conf.d" 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"
}

View File

@@ -1,61 +1,183 @@
#!/usr/bin/env bash #!/usr/bin/env bash
#=======================================================================# #=======================================================================#
# Copyright (C) 2020 - 2022 Dominik Willner <th33xitus@gmail.com> # # Copyright (C) 2020 - 2023 Dominik Willner <th33xitus@gmail.com> #
# # # #
# This file is part of KIAUH - Klipper Installation And Update Helper # # This file is part of KIAUH - Klipper Installation And Update Helper #
# https://github.com/th33xitus/kiauh # # https://github.com/dw-0/kiauh #
# # # #
# This file may be distributed under the terms of the GNU GPLv3 license # # This file may be distributed under the terms of the GNU GPLv3 license #
#=======================================================================# #=======================================================================#
set -e set -e
#TODO (multi instance):
# if the klipper installer is started another time while other klipper
# instances are detected, ask if new instances should be added
#=================================================# #=================================================#
#================ INSTALL KLIPPER ================# #================ INSTALL KLIPPER ================#
#=================================================# #=================================================#
### check for existing klipper service installations ###
function klipper_initd() { # this function detects all installed klipper
local services # systemd instances and returns their absolute path
services=$(find "${INITD}" -maxdepth 1 -regextype posix-extended -regex "${INITD}/klipper(-[^0])?[0-9]*" | sort)
echo "${services}"
}
function klipper_systemd() { function klipper_systemd() {
local services local services
services=$(find "${SYSTEMD}" -maxdepth 1 -regextype posix-extended -regex "${SYSTEMD}/klipper(-[0-9a-zA-Z]+)?.service" | sort) local blacklist
local ignore
local match
###
# any service that uses "klipper" in its own name but isn't a full klipper service must be blacklisted using
# this variable, otherwise they will be falsely recognized as klipper instances. E.g. "klipper-mcu.service"
# is not a klipper service, but related to klippers linux mcu, which also requires its own service file, hence
# it must be blacklisted.
blacklist="mcu"
ignore="${SYSTEMD}/klipper-(${blacklist}).service"
match="${SYSTEMD}/klipper(-[0-9a-zA-Z]+)?.service"
services=$(find "${SYSTEMD}" -maxdepth 1 -regextype awk ! -regex "${ignore}" -regex "${match}" | sort)
echo "${services}" echo "${services}"
} }
function klipper_exists() { function start_klipper_setup() {
local services local klipper_systemd_services
[[ -n $(klipper_initd) ]] && services+="$(klipper_initd) " local python_version
[[ -n $(klipper_systemd) ]] && services+="$(klipper_systemd)" local instance_count
echo "${services}" local instance_names
} local use_custom_names
local input
local regex
local blacklist
local error
function klipper_setup_dialog() { status_msg "Initializing Klipper installation ...\n"
status_msg "Initializing Klipper installation ..."
local klipper_services
local python_version="${1}" user_input=()
klipper_services=$(klipper_exists)
user_input+=("${python_version}")
### return early if klipper already exists ### return early if klipper already exists
if [[ -n ${klipper_services} ]]; then klipper_systemd_services=$(klipper_systemd)
local error="At least one Klipper service is already installed:"
for s in ${klipper_services}; do if [[ -n ${klipper_systemd_services} ]]; then
error="At least one Klipper service is already installed:"
for s in ${klipper_systemd_services}; do
log_info "Found Klipper service: ${s}" log_info "Found Klipper service: ${s}"
error="${error}\n ➔ ${s}" error="${error}\n ➔ ${s}"
done done
fi
[[ -n ${error} ]] && print_error "${error}" && return
print_error "${error}" && return ### user selection for python version
print_dialog_user_select_python_version
while true; do
read -p "${cyan}###### Select Python version:${white} " -i "1" -e input
case "${input}" in
1)
select_msg "Python 3.x\n"
python_version=3
break;;
2)
select_msg "Python 2.7\n"
python_version=2
break;;
B|b)
clear; install_menu; break;;
*)
error_msg "Invalid Input!\n";;
esac
done && input=""
### user selection for instance count
print_dialog_user_select_instance_count
regex="^[1-9][0-9]*$"
while [[ ! ${input} =~ ${regex} ]]; do
read -p "${cyan}###### Number of Klipper instances to set up:${white} " -i "1" -e input
if [[ ${input} =~ ${regex} ]]; then
instance_count="${input}"
select_msg "Instance count: ${instance_count}\n"
break
elif [[ ${input} == "B" || ${input} == "b" ]]; then
install_menu
else
error_msg "Invalid Input!\n"
fi
done && input=""
### user selection for custom names
use_custom_names="false"
if (( instance_count > 1 )); then
print_dialog_user_select_custom_name_bool
while true; do
read -p "${cyan}###### Assign custom names? (y/N):${white} " input
case "${input}" in
Y|y|Yes|yes)
select_msg "Yes\n"
use_custom_names="true"
break;;
N|n|No|no|"")
select_msg "No\n"
break;;
B|b)
clear; install_menu; break;;
*)
error_msg "Invalid Input!\n";;
esac
done && input=""
else
instance_names+=("printer")
fi fi
### ask for amount of instances to create ### user selection for setting the actual custom names
shopt -s nocasematch
if (( instance_count > 1 )) && [[ ${use_custom_names} == "true" ]]; then
local i
i=1
regex="^[0-9a-zA-Z]+$"
blacklist="mcu"
while [[ ! ${input} =~ ${regex} || ${input} =~ ${blacklist} || ${i} -le ${instance_count} ]]; do
read -p "${cyan}###### Name for instance #${i}:${white} " input
if [[ ${input} =~ ${blacklist} ]]; then
error_msg "Name not allowed! You are trying to use a reserved name."
elif [[ ${input} =~ ${regex} && ! ${input} =~ ${blacklist} ]]; then
select_msg "Name: ${input}\n"
if [[ ${input} =~ ^[0-9]+$ ]]; then
instance_names+=("printer_${input}")
else
instance_names+=("${input}")
fi
i=$(( i + 1 ))
else
error_msg "Invalid Input!\n"
fi
done && input=""
elif (( instance_count > 1 )) && [[ ${use_custom_names} == "false" ]]; then
for (( i=1; i <= instance_count; i++ )); do
instance_names+=("printer_${i}")
done
fi
shopt -u nocasematch
(( instance_count > 1 )) && status_msg "Installing ${instance_count} Klipper instances ..."
(( instance_count == 1 )) && status_msg "Installing single Klipper instance ..."
run_klipper_setup "${python_version}" "${instance_names[@]}"
}
function print_dialog_user_select_python_version() {
top_border
echo -e "| Please select your preferred Python version. | "
echo -e "| The recommended version is Python 3.x. | "
hr
echo -e "| 1) [Python 3.x] (recommended) | "
echo -e "| 2) [Python 2.7] ${yellow}(legacy)${white} | "
back_footer
}
function print_dialog_user_select_instance_count() {
top_border top_border
echo -e "| Please select the number of Klipper instances to set |" echo -e "| Please select the number of Klipper instances to set |"
echo -e "| up. The number of Klipper instances will determine |" echo -e "| up. The number of Klipper instances will determine |"
@@ -63,177 +185,38 @@ function klipper_setup_dialog() {
blank_line blank_line
echo -e "| ${yellow}WARNING:${white} |" echo -e "| ${yellow}WARNING:${white} |"
echo -e "| ${yellow}Setting up too many instances may crash your system.${white} |" echo -e "| ${yellow}Setting up too many instances may crash your system.${white} |"
bottom_border back_footer
### ask for amount of instances
local klipper_count re="^[1-9][0-9]*$"
while [[ ! ${klipper_count} =~ ${re} ]]; do
read -p "${cyan}###### Number of Klipper instances to set up:${white} " -i "1" -e klipper_count
### break if input is valid
[[ ${klipper_count} =~ ${re} ]] && break
### error messages on invalid input
error_msg "Input not a number"
done && select_msg "${klipper_count}"
user_input+=("${klipper_count}")
### confirm instance amount
local yn
while true; do
read -p "${cyan}###### Install ${klipper_count} instance(s)? (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 Klipper setup ...\n"
return;;
*)
error_msg "Invalid Input!";;
esac
done
### ask for custom names
if (( klipper_count > 1 )); then
local custom_names="false"
top_border
echo -e "| You can give each instance a custom name or skip. |"
echo -e "| If skipped, KIAUH will automatically assign an index |"
echo -e "| to each instance in ascending order, starting at '1'. |"
blank_line
echo -e "| Info: |"
echo -e "| Only alphanumeric characters will be allowed. |"
bottom_border
while true; do
read -p "${cyan}###### Use custom names? (y/N):${white} " yn
case "${yn}" in
Y|y|Yes|yes)
select_msg "Yes"
custom_names="true"
break;;
N|n|No|no|"")
select_msg "No"
break;;
*)
error_msg "Invalid Input!";;
esac
done
### get user input for custom names
if [[ ${custom_names} == "true" ]]; then
local i=1 name re="^[0-9a-zA-Z]+$"
while [[ ! ${name} =~ ${re} || ${i} -le ${klipper_count} ]]; do
read -p "${cyan}###### Name for instance #${i}:${white} " name
if [[ ${name} =~ ${re} ]]; then
select_msg "Name: ${name}"
user_input+=("${name}")
i=$(( i + 1 ))
else
error_msg "Invalid Input!"
fi
done
else
### if no custom names are used, add the respective amount of indices to the user_input array
for (( i=1; i <= klipper_count; i++ )); do
user_input+=("${i}")
done
fi
fi
(( klipper_count > 1 )) && status_msg "Installing ${klipper_count} Klipper instances ..."
(( klipper_count == 1 )) && status_msg "Installing single Klipper instance ..."
klipper_setup "${user_input[@]}"
} }
function install_klipper_packages() { function print_dialog_user_select_custom_name_bool() {
local packages python_version="${1}" top_border
local install_script="${KLIPPER_DIR}/scripts/install-debian.sh" echo -e "| You can now assign a custom name to each instance. |"
echo -e "| If skipped, each instance will get an index assigned |"
status_msg "Reading dependencies..." echo -e "| in ascending order, starting at index '1'. |"
# shellcheck disable=SC2016 blank_line
packages=$(grep "PKGLIST=" "${install_script}" | cut -d'"' -f2 | sed 's/\${PKGLIST}//g' | tr -d '\n') echo -e "| Info: |"
### add dfu-util for octopi-images echo -e "| Only alphanumeric characters for names are allowed! |"
packages+=" dfu-util" back_footer
### add dbus requirement for DietPi distro
[[ -e "/boot/dietpi/.version" ]] && packages+=" dbus"
if [[ ${python_version} == "python3" ]]; then
### replace python-dev with python3-dev if python3 was selected
packages="${packages//python-dev/python3-dev}"
else
### package name 'python-dev' is deprecated (-> no installation candidate) on more modern linux distros
packages="${packages//python-dev/python2-dev}"
fi
echo "${cyan}${packages}${white}" | tr '[:space:]' '\n'
read -r -a packages <<< "${packages}"
### Update system package info
status_msg "Updating package lists..."
if ! sudo apt-get update --allow-releaseinfo-change; then
log_error "failure while updating package lists"
error_msg "Updating package lists failed!"
exit 1
fi
### Install required packages
status_msg "Installing required packages..."
if ! sudo apt-get install --yes "${packages[@]}"; then
log_error "failure while installing required klipper packages"
error_msg "Installing required packages failed!"
exit 1
fi
} }
function create_klipper_virtualenv() { function run_klipper_setup() {
local python_version="${1}"
[[ ${python_version} == "python2" ]] && \
status_msg "Installing $(python2 -V) virtual environment..."
[[ ${python_version} == "python3" ]] && \
status_msg "Installing $(python3 -V) virtual environment..."
### remove klippy-env if it already exists
[[ -d ${KLIPPY_ENV} ]] && rm -rf "${KLIPPY_ENV}"
if [[ ${python_version} == "python2" ]]; then
if virtualenv -p python2 "${KLIPPY_ENV}"; then
"${KLIPPY_ENV}"/bin/pip install -r "${KLIPPER_DIR}"/scripts/klippy-requirements.txt
else
log_error "failure while creating python2 klippy-env"
error_msg "Creation of Klipper virtualenv failed!"
exit 1
fi
fi
if [[ ${python_version} == "python3" ]]; then
if virtualenv -p python3 "${KLIPPY_ENV}"; then
"${KLIPPY_ENV}"/bin/pip install -U pip
"${KLIPPY_ENV}"/bin/pip install -r "${KLIPPER_DIR}"/scripts/klippy-requirements.txt
else
log_error "failure while creating python3 klippy-env"
error_msg "Creation of Klipper virtualenv failed!"
exit 1
fi
fi
return
}
function klipper_setup() {
read_kiauh_ini "${FUNCNAME[0]}" read_kiauh_ini "${FUNCNAME[0]}"
### index 0: python version, index 1: instance count, index 2-n: instance names (optional)
local user_input=("${@}") local python_version=${1}
local python_version="${user_input[0]}" && unset "user_input[0]" local instance_names
local instance_arr=("${user_input[@]}") && unset "user_input[@]" local confirm
local custom_repo="${custom_klipper_repo}" local custom_repo
local custom_branch="${custom_klipper_repo_branch}" local custom_branch
local dep
shift 1
read -r -a instance_names <<< "${@}"
custom_repo="${custom_klipper_repo}"
custom_branch="${custom_klipper_repo_branch}"
dep=(git)
### checking dependencies ### checking dependencies
local dep=(git)
dependency_check "${dep[@]}" dependency_check "${dep[@]}"
### step 1: clone klipper ### step 1: clone klipper
@@ -243,24 +226,24 @@ function klipper_setup() {
install_klipper_packages "${python_version}" install_klipper_packages "${python_version}"
create_klipper_virtualenv "${python_version}" create_klipper_virtualenv "${python_version}"
### step 3: create gcode_files and logs folder ### step 3: create klipper instances
[[ ! -d "${HOME}/gcode_files" ]] && mkdir -p "${HOME}/gcode_files" for instance in "${instance_names[@]}"; do
[[ ! -d ${KLIPPER_LOGS} ]] && mkdir -p "${KLIPPER_LOGS}" create_klipper_service "${instance}"
done
### step 4: create klipper instances ### step 4: enable and start all instances
create_klipper_service "${instance_arr[@]}"
### step 5: enable and start all instances
do_action_service "enable" "klipper" do_action_service "enable" "klipper"
do_action_service "start" "klipper" do_action_service "start" "klipper"
### step 6: check for dialout group membership ### step 5: check for dialout group membership
check_usergroups check_usergroups
### confirm message ### confirm message
local confirm="" (( ${#instance_names[@]} == 1 )) && confirm="Klipper has been set up!"
(( instance_arr[0] == 1 )) && confirm="Klipper has been set up!" (( ${#instance_names[@]} > 1 )) && confirm="${#instance_names[@]} Klipper instances have been set up!"
(( instance_arr[0] > 1 )) && confirm="${instance_arr[0]} Klipper instances have been set up!"
### finalizing the setup with writing instance names to the kiauh.ini
set_multi_instance_names
print_confirm "${confirm}" && return print_confirm "${confirm}" && return
} }
@@ -288,81 +271,125 @@ function clone_klipper() {
fi fi
} }
function write_klipper_service() { function create_klipper_virtualenv() {
local i=${1} cfg=${2} log=${3} printer=${4} uds=${5} service=${6} local python_version="${1}"
local service_template="${KIAUH_SRCDIR}/resources/klipper.service"
[[ -d ${KLIPPY_ENV} ]] && rm -rf "${KLIPPY_ENV}"
status_msg "Installing $("python${python_version}" -V) virtual environment..."
if virtualenv -p "python${python_version}" "${KLIPPY_ENV}"; then
(( python_version == 3 )) && "${KLIPPY_ENV}"/bin/pip install -U pip
"${KLIPPY_ENV}"/bin/pip install -r "${KLIPPER_DIR}"/scripts/klippy-requirements.txt
else
log_error "failure while creating python3 klippy-env"
error_msg "Creation of Klipper virtualenv failed!"
exit 1
fi
}
###
# extracts the required packages from the
# install-debian.sh script and installs them
#
# @param {string}: python_version - klipper-env python version
#
function install_klipper_packages() {
local packages log_name="Klipper" python_version="${1}"
local install_script="${KLIPPER_DIR}/scripts/install-debian.sh"
status_msg "Reading dependencies..."
# shellcheck disable=SC2016
packages=$(grep "PKGLIST=" "${install_script}" | cut -d'"' -f2 | sed 's/\${PKGLIST}//g' | tr -d '\n')
### add dfu-util for octopi-images
packages+=" dfu-util"
### add dbus requirement for DietPi distro
[[ -e "/boot/dietpi/.version" ]] && packages+=" dbus"
if (( python_version == 3 )); then
### replace python-dev with python3-dev if python3 was selected
packages="${packages//python-dev/python3-dev}"
elif (( python_version == 2 )); then
### package name 'python-dev' is deprecated (-> no installation candidate) on more modern linux distros
packages="${packages//python-dev/python2-dev}"
else
log_error "Internal Error: missing parameter 'python_version' during function call of ${FUNCNAME[0]}"
error_msg "Internal Error: missing parameter 'python_version' during function call of ${FUNCNAME[0]}"
exit 1
fi
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 create_klipper_service() {
local instance_name=${1}
local printer_data
local cfg_dir
local cfg
local log
local klippy_serial
local klippy_socket
local env_file
local service
local service_template
local env_template
local suffix
printer_data="${HOME}/${instance_name}_data"
cfg_dir="${printer_data}/config"
cfg="${cfg_dir}/printer.cfg"
log="${printer_data}/logs/klippy.log"
klippy_serial="${printer_data}/comms/klippy.serial"
klippy_socket="${printer_data}/comms/klippy.sock"
env_file="${printer_data}/systemd/klipper.env"
if [[ ${instance_name} == "printer" ]]; then
suffix="${instance_name//printer/}"
else
suffix="-${instance_name//printer_/}"
fi
create_required_folders "${printer_data}"
service_template="${KIAUH_SRCDIR}/resources/klipper.service"
env_template="${KIAUH_SRCDIR}/resources/klipper.env"
service="${SYSTEMD}/klipper${suffix}.service"
### replace all placeholders
if [[ ! -f ${service} ]]; then if [[ ! -f ${service} ]]; then
status_msg "Creating Klipper Service ${i} ..." status_msg "Create Klipper service file ..."
sudo cp "${service_template}" "${service}" sudo cp "${service_template}" "${service}"
[[ -z ${i} ]] && sudo sed -i "s| for instance klipper-%INST%||" "${service}" sudo cp "${env_template}" "${env_file}"
[[ -n ${i} ]] && sudo sed -i "s|%INST%|${i}|" "${service}" sudo sed -i "s|%USER%|${USER}|g; s|%KLIPPER_DIR%|${KLIPPER_DIR}|; s|%ENV%|${KLIPPY_ENV}|; s|%ENV_FILE%|${env_file}|" "${service}"
sudo sed -i "s|%USER%|${USER}|; s|%ENV%|${KLIPPY_ENV}|; s|%DIR%|${KLIPPER_DIR}|" "${service}" sudo sed -i "s|%USER%|${USER}|; s|%KLIPPER_DIR%|${KLIPPER_DIR}|; s|%LOG%|${log}|; s|%CFG%|${cfg}|; s|%PRINTER%|${klippy_serial}|; s|%UDS%|${klippy_socket}|" "${env_file}"
sudo sed -i "s|%LOG%|${log}|; s|%CFG%|${cfg}|; s|%PRINTER%|${printer}|; s|%UDS%|${uds}|" "${service}"
ok_msg "Klipper service file created!"
fi
if [[ ! -f ${cfg} ]]; then
write_example_printer_cfg "${cfg}"
fi fi
} }
function write_example_printer_cfg() { function write_example_printer_cfg() {
local cfg_dir=${1} cfg=${2} local cfg=${1}
local cfg_template="${KIAUH_SRCDIR}/resources/printer.cfg" local cfg_template
### create a config directory if it doesn't exist cfg_template="${KIAUH_SRCDIR}/resources/example.printer.cfg"
if [[ ! -d ${cfg_dir} ]]; then
status_msg "Creating '${cfg_dir}' ..."
mkdir -p "${cfg_dir}"
fi
### create a minimal config if there is no printer.cfg
if [[ ! -f ${cfg} ]]; then
status_msg "Creating minimal example printer.cfg ..."
cp "${cfg_template}" "${cfg}"
fi
}
function create_klipper_service() {
local input=("${@}")
local klipper_count=${input[0]} && unset "input[0]"
local names=("${input[@]}") && unset "input[@]"
local cfg_dir cfg log printer uds service
if (( klipper_count == 1 )) && [[ ${#names[@]} -eq 0 ]]; then
cfg_dir="${KLIPPER_CONFIG}"
cfg="${cfg_dir}/printer.cfg"
log="${KLIPPER_LOGS}/klippy.log"
printer="/tmp/printer"
uds="/tmp/klippy_uds"
service="${SYSTEMD}/klipper.service"
### write single instance service
write_klipper_service "" "${cfg}" "${log}" "${printer}" "${uds}" "${service}"
write_example_printer_cfg "${cfg_dir}" "${cfg}"
ok_msg "Klipper instance created!"
elif (( klipper_count >= 1 )) && [[ ${#names[@]} -gt 0 ]]; then
local j=0 re="^[1-9][0-9]*$"
for (( i=1; i <= klipper_count; i++ )); do
### overwrite config folder if name is only a number
if [[ ${names[j]} =~ ${re} ]]; then
cfg_dir="${KLIPPER_CONFIG}/printer_${names[${j}]}"
else
cfg_dir="${KLIPPER_CONFIG}/${names[${j}]}"
fi
cfg="${cfg_dir}/printer.cfg"
log="${KLIPPER_LOGS}/klippy-${names[${j}]}.log"
printer="/tmp/printer-${names[${j}]}"
uds="/tmp/klippy_uds-${names[${j}]}"
service="${SYSTEMD}/klipper-${names[${j}]}.service"
### write multi instance service
write_klipper_service "${names[${j}]}" "${cfg}" "${log}" "${printer}" "${uds}" "${service}"
write_example_printer_cfg "${cfg_dir}" "${cfg}"
ok_msg "Klipper instance 'klipper-${names[${j}]}' created!"
j=$(( j + 1 ))
done && unset j
status_msg "Creating minimal example printer.cfg ..."
if cp "${cfg_template}" "${cfg}"; then
ok_msg "Minimal example printer.cfg created!"
else else
return 1 error_msg "Couldn't create minimal example printer.cfg!"
fi fi
} }
@@ -370,71 +397,53 @@ function create_klipper_service() {
#================ REMOVE KLIPPER ================# #================ REMOVE KLIPPER ================#
#================================================# #================================================#
function remove_klipper_sysvinit() { function remove_klipper_service() {
[[ ! -e "${INITD}/klipper" ]] && return
status_msg "Removing Klipper SysVinit service ..."
sudo systemctl stop klipper
sudo update-rc.d -f klipper remove
sudo rm -f "${INITD}/klipper" "${ETCDEF}/klipper"
ok_msg "Klipper SysVinit service removed!"
}
function remove_klipper_systemd() {
[[ -z $(klipper_systemd) ]] && return [[ -z $(klipper_systemd) ]] && return
status_msg "Removing Klipper Systemd Services ..." status_msg "Removing Klipper services ..."
for service in $(klipper_systemd | cut -d"/" -f5); do for service in $(klipper_systemd | cut -d"/" -f5); do
status_msg "Removing ${service} ..." status_msg "Removing ${service} ..."
sudo systemctl stop "${service}" sudo systemctl stop "${service}"
sudo systemctl disable "${service}" sudo systemctl disable "${service}"
sudo rm -f "${SYSTEMD}/${service}" sudo rm -f "${SYSTEMD}/${service}"
ok_msg "Done!" sudo systemctl daemon-reload
sudo systemctl reset-failed
done done
### reloading units ok_msg "All Klipper services removed!"
sudo systemctl daemon-reload
sudo systemctl reset-failed
ok_msg "Klipper Service removed!"
} }
function remove_klipper_logs() { function find_instance_files() {
local files regex="klippy(-[0-9a-zA-Z]+)?\.log(.*)?" local target_folder=${1}
files=$(find "${KLIPPER_LOGS}" -maxdepth 1 -regextype posix-extended -regex "${KLIPPER_LOGS}/${regex}" 2> /dev/null | sort) local target_name=${2}
if [[ -n ${files} ]]; then
for file in ${files}; do
status_msg "Removing ${file} ..."
rm -f "${file}"
ok_msg "${file} removed!"
done
fi
}
function remove_klipper_uds() {
local files
files=$(find /tmp -maxdepth 1 -regextype posix-extended -regex "/tmp/klippy_uds(-[0-9a-zA-Z]+)?" | 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_klipper_printer() {
local files local files
files=$(find /tmp -maxdepth 1 -regextype posix-extended -regex "/tmp/printer(-[0-9a-zA-Z]+)?" | sort) readarray -t files < <(find "${HOME}" -regex "${HOME}/[A-Za-z0-9_]+_data/${target_folder}/${target_name}" | sort)
if [[ -n ${files} ]]; then
for file in ${files}; do echo -e "${files[@]}"
status_msg "Removing ${file} ..." }
rm -f "${file}"
ok_msg "${file} removed!" function find_legacy_klipper_logs() {
done local files
fi local regex="klippy(-[0-9a-zA-Z]+)?\.log(.*)?"
readarray -t files < <(find "${HOME}/klipper_logs" -maxdepth 1 -regextype posix-extended -regex "${HOME}/klipper_logs/${regex}" 2> /dev/null | sort)
echo -e "${files[@]}"
}
function find_legacy_klipper_uds() {
local files
readarray -t files < <(find /tmp -maxdepth 1 -regextype posix-extended -regex "/tmp/klippy_uds(-[0-9a-zA-Z]+)?" | sort)
echo -e "${files[@]}"
}
function find_legacy_klipper_printer() {
local files
readarray -t files < <(find /tmp -maxdepth 1 -regextype posix-extended -regex "/tmp/printer(-[0-9a-zA-Z]+)?" | sort)
echo -e "${files[@]}"
} }
function remove_klipper_dir() { function remove_klipper_dir() {
@@ -453,38 +462,68 @@ function remove_klipper_env() {
ok_msg "Directory removed!" ok_msg "Directory removed!"
} }
###
# takes in a string of space separated absolute
# filepaths and removes those files one after another
#
function remove_files() {
local files
read -r -a files <<< "${@}"
if (( ${#files[@]} > 0 )); then
for file in "${files[@]}"; do
status_msg "Removing ${file} ..."
rm -f "${file}"
ok_msg "${file} removed!"
done
fi
}
function remove_klipper() { function remove_klipper() {
remove_klipper_sysvinit remove_klipper_service
remove_klipper_systemd remove_files "$(find_instance_files "systemd" "klipper.env")"
remove_klipper_logs remove_files "$(find_instance_files "logs" "klippy.log.*")"
remove_klipper_uds remove_files "$(find_instance_files "comms" "klippy.sock")"
remove_klipper_printer remove_files "$(find_instance_files "comms" "klippy.serial")"
remove_files "$(find_legacy_klipper_logs)"
remove_files "$(find_legacy_klipper_uds)"
remove_files "$(find_legacy_klipper_printer)"
remove_klipper_dir remove_klipper_dir
remove_klipper_env remove_klipper_env
local confirm="Klipper was successfully removed!" print_confirm "Klipper was successfully removed!" && return
print_confirm "${confirm}" && return
} }
#================================================# #================================================#
#================ UPDATE KLIPPER ================# #================ UPDATE KLIPPER ================#
#================================================# #================================================#
###
# stops klipper, performs a git pull, installs
# possible new dependencies, then restarts klipper
#
function update_klipper() { function update_klipper() {
read_kiauh_ini "${FUNCNAME[0]}" read_kiauh_ini "${FUNCNAME[0]}"
local py_ver
local custom_repo="${custom_klipper_repo}" local custom_repo="${custom_klipper_repo}"
local custom_branch="${custom_klipper_repo_branch}" local custom_branch="${custom_klipper_repo_branch}"
py_ver=$(get_klipper_python_ver)
do_action_service "stop" "klipper" do_action_service "stop" "klipper"
if [[ ! -d ${KLIPPER_DIR} ]]; then if [[ ! -d ${KLIPPER_DIR} ]]; then
clone_klipper "${custom_repo}" "${custom_branch}" clone_klipper "${custom_repo}" "${custom_branch}"
else else
backup_before_update "klipper" backup_before_update "klipper"
status_msg "Updating Klipper ..." status_msg "Updating Klipper ..."
cd "${KLIPPER_DIR}" && git pull cd "${KLIPPER_DIR}" && git pull
### read PKGLIST and install possible new dependencies ### read PKGLIST and install possible new dependencies
install_klipper_packages install_klipper_packages "${py_ver}"
### install possible new python dependencies ### install possible new python dependencies
"${KLIPPY_ENV}"/bin/pip install -r "${KLIPPER_DIR}/scripts/klippy-requirements.txt" "${KLIPPY_ENV}"/bin/pip install -r "${KLIPPER_DIR}/scripts/klippy-requirements.txt"
fi fi
@@ -501,12 +540,6 @@ function get_klipper_status() {
local sf_count status py_ver local sf_count status py_ver
sf_count="$(klipper_systemd | wc -w)" sf_count="$(klipper_systemd | wc -w)"
### detect an existing "legacy" klipper init.d installation
if [[ $(klipper_systemd | wc -w) -eq 0 ]] \
&& [[ $(klipper_initd | wc -w) -ge 1 ]]; then
sf_count=1
fi
py_ver=$(get_klipper_python_ver) py_ver=$(get_klipper_python_ver)
### remove the "SERVICE" entry from the data array if a klipper service is installed ### remove the "SERVICE" entry from the data array if a klipper service is installed
@@ -547,13 +580,18 @@ function get_remote_klipper_commit() {
[[ ! -d ${KLIPPER_DIR} || ! -d "${KLIPPER_DIR}/.git" ]] && return [[ ! -d ${KLIPPER_DIR} || ! -d "${KLIPPER_DIR}/.git" ]] && return
local commit local commit
local branch
read_kiauh_ini "${FUNCNAME[0]}"
branch="${custom_klipper_repo_branch}"
[[ -z ${branch} ]] && branch="master"
cd "${KLIPPER_DIR}" && git fetch origin -q cd "${KLIPPER_DIR}" && git fetch origin -q
commit=$(git describe origin/master --always --tags | cut -d "-" -f 1,2) commit=$(git describe "origin/${branch}" --always --tags | cut -d "-" -f 1,2)
echo "${commit}" echo "${commit}"
} }
function compare_klipper_versions() { function compare_klipper_versions() {
unset KLIPPER_UPDATE_AVAIL
local versions local_ver remote_ver local versions local_ver remote_ver
local_ver="$(get_local_klipper_commit)" local_ver="$(get_local_klipper_commit)"
remote_ver="$(get_remote_klipper_commit)" remote_ver="$(get_remote_klipper_commit)"
@@ -561,12 +599,11 @@ function compare_klipper_versions() {
if [[ ${local_ver} != "${remote_ver}" ]]; then if [[ ${local_ver} != "${remote_ver}" ]]; then
versions="${yellow}$(printf " %-14s" "${local_ver}")${white}" versions="${yellow}$(printf " %-14s" "${local_ver}")${white}"
versions+="|${green}$(printf " %-13s" "${remote_ver}")${white}" versions+="|${green}$(printf " %-13s" "${remote_ver}")${white}"
# add klipper to the update all array for the update all function in the updater # add klipper to application_updates_available in kiauh.ini
KLIPPER_UPDATE_AVAIL="true" && update_arr+=(update_klipper) add_to_application_updates "klipper"
else else
versions="${green}$(printf " %-14s" "${local_ver}")${white}" versions="${green}$(printf " %-14s" "${local_ver}")${white}"
versions+="|${green}$(printf " %-13s" "${remote_ver}")${white}" versions+="|${green}$(printf " %-13s" "${remote_ver}")${white}"
KLIPPER_UPDATE_AVAIL="false"
fi fi
echo "${versions}" echo "${versions}"
@@ -576,24 +613,15 @@ function compare_klipper_versions() {
#=================== HELPERS ====================# #=================== HELPERS ====================#
#================================================# #================================================#
function get_klipper_cfg_dir() { ###
local cfg_dir # reads the python version from the klipper virtual environment
read_kiauh_ini "${FUNCNAME[0]}" #
# @output: writes the python major version to STDOUT
if [[ -z ${custom_klipper_cfg_loc} ]]; then #
cfg_dir="${HOME}/klipper_config"
else
cfg_dir="${custom_klipper_cfg_loc}"
fi
echo "${cfg_dir}"
}
### returns the major python version the klippy-env was created with
function get_klipper_python_ver() { function get_klipper_python_ver() {
[[ ! -d ${KLIPPY_ENV} ]] && return [[ ! -d ${KLIPPY_ENV} ]] && return
local version local version
version=$("${KLIPPY_ENV}"/bin/python --version 2>&1 | cut -d" " -f2 | cut -d"." -f1) version=$("${KLIPPY_ENV}"/bin/python --version 2>&1 | cut -d" " -f2 | cut -d"." -f1)
echo "${version}" echo "${version}"
} }

View File

@@ -1,10 +1,10 @@
#!/usr/bin/env bash #!/usr/bin/env bash
#=======================================================================# #=======================================================================#
# Copyright (C) 2020 - 2022 Dominik Willner <th33xitus@gmail.com> # # Copyright (C) 2020 - 2023 Dominik Willner <th33xitus@gmail.com> #
# # # #
# This file is part of KIAUH - Klipper Installation And Update Helper # # This file is part of KIAUH - Klipper Installation And Update Helper #
# https://github.com/th33xitus/kiauh # # https://github.com/dw-0/kiauh #
# # # #
# This file may be distributed under the terms of the GNU GPLv3 license # # This file may be distributed under the terms of the GNU GPLv3 license #
#=======================================================================# #=======================================================================#
@@ -30,7 +30,7 @@ function install_klipperscreen() {
fi fi
### first, we create a backup of the full klipper_config dir - safety first! ### first, we create a backup of the full klipper_config dir - safety first!
backup_klipper_config_dir backup_config_dir
### install KlipperScreen ### install KlipperScreen
klipperscreen_setup klipperscreen_setup
@@ -180,7 +180,6 @@ function get_remote_klipperscreen_commit() {
} }
function compare_klipperscreen_versions() { function compare_klipperscreen_versions() {
unset KLIPPERSCREEN_UPDATE_AVAIL
local versions local_ver remote_ver local versions local_ver remote_ver
local_ver="$(get_local_klipperscreen_commit)" local_ver="$(get_local_klipperscreen_commit)"
remote_ver="$(get_remote_klipperscreen_commit)" remote_ver="$(get_remote_klipperscreen_commit)"
@@ -188,12 +187,11 @@ function compare_klipperscreen_versions() {
if [[ ${local_ver} != "${remote_ver}" ]]; then if [[ ${local_ver} != "${remote_ver}" ]]; then
versions="${yellow}$(printf " %-14s" "${local_ver}")${white}" versions="${yellow}$(printf " %-14s" "${local_ver}")${white}"
versions+="|${green}$(printf " %-13s" "${remote_ver}")${white}" versions+="|${green}$(printf " %-13s" "${remote_ver}")${white}"
# add klipperscreen to the update all array for the update all function in the updater # add moonraker to application_updates_available in kiauh.ini
KLIPPERSCREEN_UPDATE_AVAIL="true" && update_arr+=(update_klipperscreen) add_to_application_updates "klipperscreen"
else else
versions="${green}$(printf " %-14s" "${local_ver}")${white}" versions="${green}$(printf " %-14s" "${local_ver}")${white}"
versions+="|${green}$(printf " %-13s" "${remote_ver}")${white}" versions+="|${green}$(printf " %-13s" "${remote_ver}")${white}"
KLIPPERSCREEN_UPDATE_AVAIL="false"
fi fi
echo "${versions}" echo "${versions}"
@@ -204,11 +202,12 @@ function compare_klipperscreen_versions() {
#================================================# #================================================#
function patch_klipperscreen_update_manager() { function patch_klipperscreen_update_manager() {
local patched="false"
local moonraker_configs local moonraker_configs
moonraker_configs=$(find "${KLIPPER_CONFIG}" -type f -name "moonraker.conf" | sort) moonraker_configs=$(find "${KLIPPER_CONFIG}" -type f -name "moonraker.conf" | sort)
for conf in ${moonraker_configs}; do for conf in ${moonraker_configs}; do
if ! grep -Eq "^\[update_manager KlipperScreen\]$" "${conf}"; then if ! grep -Eq "^\[update_manager KlipperScreen\]\s*$" "${conf}"; then
### add new line to conf if it doesn't end with one ### add new line to conf if it doesn't end with one
[[ $(tail -c1 "${conf}" | wc -l) -eq 0 ]] && echo "" >> "${conf}" [[ $(tail -c1 "${conf}" | wc -l) -eq 0 ]] && echo "" >> "${conf}"
@@ -226,5 +225,11 @@ install_script: scripts/KlipperScreen-install.sh
MOONRAKER_CONF MOONRAKER_CONF
fi fi
patched="true"
done done
if [[ ${patched} == "true" ]]; then
do_action_service "restart" "moonraker"
fi
} }

View File

@@ -1,10 +1,10 @@
#!/usr/bin/env bash #!/usr/bin/env bash
#=======================================================================# #=======================================================================#
# Copyright (C) 2020 - 2022 Dominik Willner <th33xitus@gmail.com> # # Copyright (C) 2020 - 2023 Dominik Willner <th33xitus@gmail.com> #
# # # #
# This file is part of KIAUH - Klipper Installation And Update Helper # # This file is part of KIAUH - Klipper Installation And Update Helper #
# https://github.com/th33xitus/kiauh # # https://github.com/dw-0/kiauh #
# # # #
# This file may be distributed under the terms of the GNU GPLv3 license # # This file may be distributed under the terms of the GNU GPLv3 license #
#=======================================================================# #=======================================================================#
@@ -16,10 +16,24 @@ set -e
#===================================================# #===================================================#
function install_mainsail() { function install_mainsail() {
### exit early if moonraker not found
if [[ -z $(moonraker_systemd) ]]; then if [[ -z $(moonraker_systemd) ]]; then
local error="Moonraker not installed! Please install Moonraker first!" local error="Moonraker not installed! It's recommended to install Moonraker first!"
print_error "${error}" && return print_error "${error}"
while true; do
local yn
read -p "${cyan}###### Proceed to install Mainsail 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 Mainsail setup ...\n"
return;;
*)
error_msg "Invalid Input!";;
esac
done
fi fi
### checking dependencies ### checking dependencies
@@ -30,7 +44,7 @@ function install_mainsail() {
status_msg "Initializing Mainsail installation ..." status_msg "Initializing Mainsail installation ..."
### first, we create a backup of the full klipper_config dir - safety first! ### first, we create a backup of the full klipper_config dir - safety first!
backup_klipper_config_dir backup_config_dir
### check for other enabled web interfaces ### check for other enabled web interfaces
unset SET_LISTEN_PORT unset SET_LISTEN_PORT
@@ -39,30 +53,6 @@ function install_mainsail() {
### check if another site already listens to port 80 ### check if another site already listens to port 80
mainsail_port_check mainsail_port_check
### ask user to install mjpg-streamer
local install_mjpg_streamer
if [[ ! -f "${SYSTEMD}/webcamd.service" ]]; then
while true; do
echo
top_border
echo -e "| Install MJGP-Streamer for webcam support? |"
bottom_border
read -p "${cyan}###### Please select (y/N):${white} " yn
case "${yn}" in
Y|y|Yes|yes)
select_msg "Yes"
install_mjpg_streamer="true"
break;;
N|n|No|no|"")
select_msg "No"
install_mjpg_streamer="false"
break;;
*)
error_msg "Invalid command!";;
esac
done
fi
### download mainsail ### download mainsail
download_mainsail download_mainsail
@@ -82,9 +72,6 @@ function install_mainsail() {
### add mainsail to the update manager in moonraker.conf ### add mainsail to the update manager in moonraker.conf
patch_mainsail_update_manager patch_mainsail_update_manager
### install mjpg-streamer
[[ ${install_mjpg_streamer} == "true" ]] && install_mjpg-streamer
fetch_webui_ports #WIP fetch_webui_ports #WIP
### confirm message ### confirm message
@@ -92,22 +79,21 @@ function install_mainsail() {
} }
function install_mainsail_macros() { function install_mainsail_macros() {
local yn
while true; do while true; do
echo echo
top_border top_border
echo -e "| It is recommended to have some important macros in |" echo -e "| It is recommended to use special macros in order to |"
echo -e "| your printer configuration to have Mainsail fully |" echo -e "| have Mainsail fully functional and working. |"
echo -e "| functional and working. |"
blank_line blank_line
echo -e "| The recommended macros for Mainsail can be seen here: |" echo -e "| The recommended macros for Mainsail can be seen here: |"
echo -e "| https://docs.mainsail.xyz/configuration#macros |" echo -e "| https://github.com/mainsail-crew/mainsail-config |"
blank_line blank_line
echo -e "| If you already have these macros in your config file, |" echo -e "| If you already use these macros skip this step. |"
echo -e "| skip this step and answer with 'no'. |"
echo -e "| Otherwise you should consider to answer with 'yes' to |" echo -e "| Otherwise you should consider to answer with 'yes' to |"
echo -e "| add the recommended example macros to your config. |" echo -e "| download the recommended macros. |"
bottom_border bottom_border
read -p "${cyan}###### Add the recommended macros? (Y/n):${white} " yn read -p "${cyan}###### Download the recommended macros? (Y/n):${white} " yn
case "${yn}" in case "${yn}" in
Y|y|Yes|yes|"") Y|y|Yes|yes|"")
select_msg "Yes" select_msg "Yes"
@@ -124,37 +110,68 @@ function install_mainsail_macros() {
} }
function download_mainsail_macros() { function download_mainsail_macros() {
local ms_cfg="https://raw.githubusercontent.com/mainsail-crew/MainsailOS/master/src/modules/mainsail/filesystem/home/pi/klipper_config/mainsail.cfg" local ms_cfg_repo path configs regex line gcode_dir
local configs path
configs=$(find "${KLIPPER_CONFIG}" -type f -name "printer.cfg" | sort)
if [[ -n ${configs} ]]; then ms_cfg_repo="https://github.com/mainsail-crew/mainsail-config.git"
for config in ${configs}; do regex="${HOME//\//\\/}\/([A-Za-z0-9_]+)\/config\/printer\.cfg"
path=$(echo "${config}" | rev | cut -d"/" -f2- | rev) configs=$(find "${HOME}" -maxdepth 3 -regextype posix-extended -regex "${regex}" | sort)
if [[ ! -f "${path}/mainsail.cfg" ]]; then
status_msg "Downloading mainsail.cfg to ${path} ..."
log_info "downloading mainsail.cfg to: ${path}"
wget "${ms_cfg}" -O "${path}/mainsail.cfg"
### replace user 'pi' with current username to prevent issues in cases where the user is not called 'pi' if [[ -z ${configs} ]]; then
log_info "modify mainsail.cfg" print_error "No printer.cfg found! Installation of Macros will be skipped ..."
sed -i "/^path: \/home\/pi\/gcode_files/ s/\/home\/pi/\/home\/${USER}/" "${path}/mainsail.cfg"
### write include to the very first line of the printer.cfg
if ! grep -Eq "^[include mainsail.cfg]$" "${path}/printer.cfg"; then
log_info "modify printer.cfg"
sed -i "1 i [include mainsail.cfg]" "${path}/printer.cfg"
fi
ok_msg "Done!"
fi
done
else
log_error "execution stopped! reason: no printer.cfg found" log_error "execution stopped! reason: no printer.cfg found"
return return
fi fi
status_msg "Cloning mainsail-config ..."
[[ -d "${HOME}/mainsail-config" ]] && rm -rf "${HOME}/mainsail-config"
if git clone "${ms_cfg_repo}" "${HOME}/mainsail-config"; then
for config in ${configs}; do
path=$(echo "${config}" | rev | cut -d"/" -f2- | rev)
if [[ -e "${path}/mainsail.cfg" && ! -h "${path}/mainsail.cfg" ]]; then
warn_msg "Attention! Existing mainsail.cfg detected!"
warn_msg "The file will be renamed to 'mainsail.bak.cfg' to be able to continue with the installation."
if ! mv "${path}/mainsail.cfg" "${path}/mainsail.bak.cfg"; then
error_msg "Renaming mainsail.cfg failed! Aborting installation ..."
return
fi
fi
if [[ -h "${path}/mainsail.cfg" ]]; then
warn_msg "Recreating symlink in ${path} ..."
rm -rf "${path}/mainsail.cfg"
fi
if ! ln -sf "${HOME}/mainsail-config/client.cfg" "${path}/mainsail.cfg"; then
error_msg "Creating symlink failed! Aborting installation ..."
return
fi
if ! grep -Eq "^\[include mainsail.cfg\]$" "${path}/printer.cfg"; then
log_info "${path}/printer.cfg"
sed -i "1 i [include mainsail.cfg]" "${path}/printer.cfg"
fi
line=$(($(grep -n "\[include mainsail.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_mainsail_config_update_manager
ok_msg "Done!"
} }
function download_mainsail() { function download_mainsail() {
local services
local url local url
url=$(get_mainsail_download_url) url=$(get_mainsail_download_url)
@@ -177,8 +194,9 @@ function download_mainsail() {
exit 1 exit 1
fi fi
### check for moonraker multi-instance and if multi-instance was found, enable mainsails remoteMode ### check for moonraker multi-instance and if no-instance or multi-instance was found, enable mainsails remoteMode
if [[ $(moonraker_systemd | wc -w) -gt 1 ]]; then services=$(moonraker_systemd)
if [[ ( -z "${services}" ) || ( $(echo "${services}" | wc -w) -gt 1 ) ]]; then
enable_mainsail_remotemode enable_mainsail_remotemode
fi fi
} }
@@ -194,7 +212,7 @@ function remove_mainsail_dir() {
rm -rf "${MAINSAIL_DIR}" && ok_msg "Directory removed!" rm -rf "${MAINSAIL_DIR}" && ok_msg "Directory removed!"
} }
function remove_mainsail_config() { function remove_mainsail_nginx_config() {
if [[ -e "/etc/nginx/sites-available/mainsail" ]]; then if [[ -e "/etc/nginx/sites-available/mainsail" ]]; then
status_msg "Removing Mainsail configuration for Nginx ..." status_msg "Removing Mainsail configuration for Nginx ..."
sudo rm "/etc/nginx/sites-available/mainsail" && ok_msg "File removed!" sudo rm "/etc/nginx/sites-available/mainsail" && ok_msg "File removed!"
@@ -219,8 +237,10 @@ function remove_mainsail_logs() {
} }
function remove_mainsail_log_symlinks() { function remove_mainsail_log_symlinks() {
local files local files regex
files=$(find "${KLIPPER_LOGS}" -name "mainsail*" 2> /dev/null | sort)
regex="${HOME//\//\\/}\/([A-Za-z0-9_]+)\/logs\/mainsail-.*"
files=$(find "${HOME}" -maxdepth 3 -regextype posix-extended -regex "${regex}" 2> /dev/null | sort)
if [[ -n ${files} ]]; then if [[ -n ${files} ]]; then
for file in ${files}; do for file in ${files}; do
@@ -231,11 +251,34 @@ function remove_mainsail_log_symlinks() {
fi fi
} }
function remove_legacy_mainsail_log_symlinks() {
local files
files=$(find "${HOME}/klipper_logs" -name "mainsail*" 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_mainsail_config() {
if [[ -d "${HOME}/mainsail-config" ]]; then
status_msg "Removing ${HOME}/mainsail-config ..."
rm -rf "${HOME}/mainsail-config"
ok_msg "${HOME}/mainsail-config removed!"
print_confirm "Mainsail-Config successfully removed!"
fi
}
function remove_mainsail() { function remove_mainsail() {
remove_mainsail_dir remove_mainsail_dir
remove_mainsail_config remove_mainsail_nginx_config
remove_mainsail_logs remove_mainsail_logs
remove_mainsail_log_symlinks remove_mainsail_log_symlinks
remove_legacy_mainsail_log_symlinks
### remove mainsail_port from ~/.kiauh.ini ### remove mainsail_port from ~/.kiauh.ini
sed -i "/^mainsail_port=/d" "${INI_FILE}" sed -i "/^mainsail_port=/d" "${INI_FILE}"
@@ -281,36 +324,40 @@ function get_mainsail_status() {
} }
function get_local_mainsail_version() { function get_local_mainsail_version() {
[[ ! -f "${MAINSAIL_DIR}/.version" ]] && return local versionfile="${MAINSAIL_DIR}/.version"
local relinfofile="${MAINSAIL_DIR}/release_info.json"
local version local version
version=$(head -n 1 "${MAINSAIL_DIR}/.version")
if [[ -f ${relinfofile} ]]; then
version=$(grep -o '"version":"[^"]*' "${relinfofile}" | grep -o '[^"]*$')
elif [[ -f ${versionfile} ]]; then
version=$(head -n 1 "${versionfile}")
fi
echo "${version}" echo "${version}"
} }
function get_remote_mainsail_version() { function get_remote_mainsail_version() {
[[ ! $(dpkg-query -f'${Status}' --show curl 2>/dev/null) = *\ installed ]] && return [[ ! $(dpkg-query -f'${Status}' --show curl 2>/dev/null) = *\ installed ]] && return
local version local tags
version=$(get_mainsail_download_url | rev | cut -d"/" -f2 | rev) tags=$(curl -s "https://api.github.com/repos/mainsail-crew/mainsail/tags" | grep "name" | cut -d'"' -f4)
echo "${version}" echo "${tags}" | head -1
} }
function compare_mainsail_versions() { function compare_mainsail_versions() {
unset MAINSAIL_UPDATE_AVAIL
local versions local_ver remote_ver local versions local_ver remote_ver
local_ver="$(get_local_mainsail_version)" local_ver="$(get_local_mainsail_version)"
remote_ver="$(get_remote_mainsail_version)" remote_ver="$(get_remote_mainsail_version)"
if [[ ${local_ver} != "${remote_ver}" ]]; then if [[ ${local_ver} != "${remote_ver}" && ${local_ver} != "" ]]; then
versions="${yellow}$(printf " %-14s" "${local_ver}")${white}" versions="${yellow}$(printf " %-14s" "${local_ver}")${white}"
versions+="|${green}$(printf " %-13s" "${remote_ver}")${white}" versions+="|${green}$(printf " %-13s" "${remote_ver}")${white}"
# add mainsail to the update all array for the update all function in the updater # add moonraker to application_updates_available in kiauh.ini
MAINSAIL_UPDATE_AVAIL="true" && update_arr+=(update_mainsail) add_to_application_updates "mainsail"
else else
versions="${green}$(printf " %-14s" "${local_ver}")${white}" versions="${green}$(printf " %-14s" "${local_ver}")${white}"
versions+="|${green}$(printf " %-13s" "${remote_ver}")${white}" versions+="|${green}$(printf " %-13s" "${remote_ver}")${white}"
MAINSAIL_UPDATE_AVAIL="false"
fi fi
echo "${versions}" echo "${versions}"
@@ -333,7 +380,7 @@ function print_theme_list() {
function ms_theme_installer_menu() { function ms_theme_installer_menu() {
local theme_list theme_author theme_repo theme_name theme_note theme_url local theme_list theme_author theme_repo theme_name theme_note theme_url
local theme_csv_url="https://raw.githubusercontent.com/mainsail-crew/docs/master/_data/themes.csv" local theme_csv_url="https://raw.githubusercontent.com/mainsail-crew/gb-docs/main/_data/themes.csv"
theme_list=$(curl -s -L "${theme_csv_url}") theme_list=$(curl -s -L "${theme_csv_url}")
top_border top_border
@@ -379,23 +426,41 @@ function ms_theme_installer_menu() {
} }
function ms_theme_install() { function ms_theme_install() {
local theme_url=${1} theme_name theme_note read_kiauh_ini "${FUNCNAME[0]}"
theme_name=${2} theme_note=${3}
local config_folders target_folders=() local theme_url
config_folders=$(find "${KLIPPER_CONFIG}" -mindepth 1 -maxdepth 1 -type d | sort) local theme_name
local theme_note
theme_url=${1}
theme_name=${2}
theme_note=${3}
### build target folder array local folder_arr
for folder in ${config_folders}; do local folder_names="${multi_instance_names}"
target_folders+=("${folder}") local target_folders=()
done
IFS=',' read -r -a folder_arr <<< "${folder_names}"
### build theme target folder array
if (( ${#folder_arr[@]} > 1 )); then
for folder in "${folder_arr[@]}"; do
### instance names/identifier of only numbers need to be prefixed with 'printer_'
if [[ ${folder} =~ ^[0-9]+$ ]]; then
target_folders+=("${HOME}/printer_${folder}_data/config")
else
target_folders+=("${HOME}/${folder}_data/config")
fi
done
else
target_folders+=("${HOME}/printer_data/config")
fi
if (( ${#target_folders[@]} > 1 )); then if (( ${#target_folders[@]} > 1 )); then
top_border top_border
echo -e "| Please select the printer you want to apply the theme |" echo -e "| Please select the printer you want to apply the theme |"
echo -e "| installation to: |" echo -e "| installation to: |"
for (( i=0; i < ${#target_folders[@]}; i++ )); do for (( i=0; i < ${#target_folders[@]}; i++ )); do
folder=$(echo "${target_folders[${i}]}" | rev | cut -d "/" -f1 | rev) folder=$(echo "${target_folders[${i}]}" | rev | cut -d "/" -f2 | cut -d"_" -f2- | rev)
printf "|${cyan}%-55s${white}|\n" " ${i}) ${folder}" printf "|${cyan}%-55s${white}|\n" " ${i}) ${folder}"
done done
bottom_border bottom_border
@@ -424,15 +489,21 @@ function ms_theme_install() {
} }
function ms_theme_delete() { function ms_theme_delete() {
local theme_folders target_folders=() local regex theme_folders target_folders=()
theme_folders=$(find "${KLIPPER_CONFIG}" -mindepth 1 -type d -name ".theme" | sort)
regex="${HOME//\//\\/}\/([A-Za-z0-9_]+)\/config\/\.theme"
theme_folders=$(find "${HOME}" -maxdepth 3 -type d -regextype posix-extended -regex "${regex}" | sort)
# theme_folders=$(find "${KLIPPER_CONFIG}" -mindepth 1 -type d -name ".theme" | sort)
### build target folder array ### build target folder array
for folder in ${theme_folders}; do for folder in ${theme_folders}; do
target_folders+=("${folder}") target_folders+=("${folder}")
done done
if (( ${#target_folders[@]} > 0 )); then if (( ${#target_folders[@]} == 0 )); then
status_msg "No Themes installed!\n"
return
elif (( ${#target_folders[@]} > 1 )); then
top_border top_border
echo -e "| Please select the printer you want to remove the |" echo -e "| Please select the printer you want to remove the |"
echo -e "| theme installation from. |" echo -e "| theme installation from. |"
@@ -449,9 +520,6 @@ function ms_theme_delete() {
[[ ${target} =~ ${re} && ${target} -lt ${#target_folders[@]} ]] && break [[ ${target} =~ ${re} && ${target} -lt ${#target_folders[@]} ]] && break
error_msg "Invalid command!" error_msg "Invalid command!"
done done
else
status_msg "No Themes installed!\n"
return
fi fi
status_msg "Removing ${target_folders[${target}]} ..." status_msg "Removing ${target_folders[${target}]} ..."
@@ -465,25 +533,28 @@ function ms_theme_delete() {
#================================================# #================================================#
function get_mainsail_download_url() { function get_mainsail_download_url() {
local tags latest_tag latest_url stable_tag stable_url url local releases_by_tag tags tag unstable_url url
tags=$(curl -s "${MAINSAIL_TAGS}" | grep "name" | cut -d'"' -f4)
### latest download url including pre-releases (alpha, beta, rc) ### latest stable download url
latest_tag=$(echo "${tags}" | head -1) url="https://github.com/mainsail-crew/mainsail/releases/latest/download/mainsail.zip"
latest_url="https://github.com/mainsail-crew/mainsail/releases/download/${latest_tag}/mainsail.zip"
### get stable mainsail download url
stable_tag=$(echo "${tags}" | grep -E "^v([0-9]+\.?){3}$" | head -1)
stable_url="https://github.com/mainsail-crew/mainsail/releases/download/${stable_tag}/mainsail.zip"
read_kiauh_ini "${FUNCNAME[0]}" read_kiauh_ini "${FUNCNAME[0]}"
if [[ ${mainsail_install_unstable} == "true" ]]; then if [[ ${mainsail_install_unstable} == "true" ]]; then
url="${latest_url}" releases_by_tag="https://api.github.com/repos/mainsail-crew/mainsail/tags"
echo "${url}" tags=$(curl -s "${releases_by_tag}" | grep "name" | cut -d'"' -f4)
else tag=$(echo "${tags}" | head -1)
url="${stable_url}"
echo "${url}" ### latest unstable download url including pre-releases (alpha, beta, rc)
unstable_url="https://github.com/mainsail-crew/mainsail/releases/download/${tag}/mainsail.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 fi
echo "${url}"
} }
function mainsail_port_check() { function mainsail_port_check() {
@@ -500,7 +571,7 @@ function mainsail_port_check() {
select_mainsail_port select_mainsail_port
fi fi
else else
DEFAULT_PORT=$(grep listen "${KIAUH_SRCDIR}/resources/klipper_webui_nginx.cfg" | head -1 | sed 's/^\s*//' | cut -d" " -f2 | cut -d";" -f1) DEFAULT_PORT=$(grep listen "${KIAUH_SRCDIR}/resources/mainsail" | head -1 | sed 's/^\s*//' | cut -d" " -f2 | cut -d";" -f1)
SET_LISTEN_PORT=${DEFAULT_PORT} SET_LISTEN_PORT=${DEFAULT_PORT}
fi fi
SET_NGINX_CFG="true" SET_NGINX_CFG="true"
@@ -548,16 +619,19 @@ function select_mainsail_port() {
function enable_mainsail_remotemode() { function enable_mainsail_remotemode() {
[[ ! -f "${MAINSAIL_DIR}/config.json" ]] && return [[ ! -f "${MAINSAIL_DIR}/config.json" ]] && return
rm -f "${MAINSAIL_DIR}/config.json" status_msg "Setting instance storage location to 'browser' ..."
echo -e "{\n \"remoteMode\":true\n}" >> "${MAINSAIL_DIR}/config.json" sed -i 's|"instancesDB": "moonraker"|"instancesDB": "browser"|' "${MAINSAIL_DIR}/config.json"
ok_msg "Done!"
} }
function patch_mainsail_update_manager() { function patch_mainsail_update_manager() {
local moonraker_configs local patched moonraker_configs regex
moonraker_configs=$(find "${KLIPPER_CONFIG}" -type f -name "moonraker.conf" | sort) 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 for conf in ${moonraker_configs}; do
if ! grep -Eq "^\[update_manager mainsail\]$" "${conf}"; then if ! grep -Eq "^\[update_manager mainsail\]\s*$" "${conf}"; then
### add new line to conf if it doesn't end with one ### add new line to conf if it doesn't end with one
[[ $(tail -c1 "${conf}" | wc -l) -eq 0 ]] && echo "" >> "${conf}" [[ $(tail -c1 "${conf}" | wc -l) -eq 0 ]] && echo "" >> "${conf}"
@@ -573,5 +647,44 @@ path: ~/mainsail
MOONRAKER_CONF MOONRAKER_CONF
fi fi
patched="true"
done done
}
if [[ ${patched} == "true" ]]; then
do_action_service "restart" "moonraker"
fi
}
function patch_mainsail_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 mainsail-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 Mainsails update manager section to moonraker.conf
status_msg "Adding Mainsail-Config to update manager in file:\n ${conf}"
/bin/sh -c "cat >> ${conf}" << MOONRAKER_CONF
[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
MOONRAKER_CONF
fi
patched="true"
done
if [[ ${patched} == "true" ]]; then
do_action_service "restart" "moonraker"
fi
}

View File

@@ -1,10 +1,10 @@
#!/usr/bin/env bash #!/usr/bin/env bash
#=======================================================================# #=======================================================================#
# Copyright (C) 2020 - 2022 Dominik Willner <th33xitus@gmail.com> # # Copyright (C) 2020 - 2023 Dominik Willner <th33xitus@gmail.com> #
# # # #
# This file is part of KIAUH - Klipper Installation And Update Helper # # This file is part of KIAUH - Klipper Installation And Update Helper #
# https://github.com/th33xitus/kiauh # # https://github.com/dw-0/kiauh #
# # # #
# This file may be distributed under the terms of the GNU GPLv3 license # # This file may be distributed under the terms of the GNU GPLv3 license #
#=======================================================================# #=======================================================================#
@@ -16,10 +16,10 @@ set -e
#=================================================# #=================================================#
function install_mjpg-streamer() { function install_mjpg-streamer() {
local webcamd="https://raw.githubusercontent.com/mainsail-crew/MainsailOS/master/src/modules/mjpgstreamer/filesystem/root/usr/local/bin/webcamd" local webcamd="${KIAUH_SRCDIR}/resources/mjpg-streamer/webcamd"
local webcam_txt="https://raw.githubusercontent.com/mainsail-crew/MainsailOS/master/src/modules/mjpgstreamer/filesystem/home/pi/klipper_config/webcam.txt" local webcam_txt="${KIAUH_SRCDIR}/resources/mjpg-streamer/webcam.txt"
local service="${KIAUH_SRCDIR}/resources/mjpg-streamer/webcamd.service"
local repo="https://github.com/jacksonliam/mjpg-streamer.git" local repo="https://github.com/jacksonliam/mjpg-streamer.git"
local service="${KIAUH_SRCDIR}/resources/webcamd.service"
### return early if webcamd.service already exists ### return early if webcamd.service already exists
if [[ -f "${SYSTEMD}/webcamd.service" ]]; then if [[ -f "${SYSTEMD}/webcamd.service" ]]; then
@@ -77,7 +77,7 @@ function install_mjpg-streamer() {
</html> </html>
EOT EOT
sudo wget "${webcamd}" -O "/usr/local/bin/webcamd" sudo cp "${webcamd}" "/usr/local/bin/webcamd"
sudo sed -i "/^config_dir=/ s|=.*|=${KLIPPER_CONFIG}|" /usr/local/bin/webcamd sudo sed -i "/^config_dir=/ s|=.*|=${KLIPPER_CONFIG}|" /usr/local/bin/webcamd
sudo sed -i "/MJPGSTREAMER_HOME/ s/pi/${USER}/" /usr/local/bin/webcamd sudo sed -i "/MJPGSTREAMER_HOME/ s/pi/${USER}/" /usr/local/bin/webcamd
sudo chmod +x /usr/local/bin/webcamd sudo chmod +x /usr/local/bin/webcamd
@@ -86,7 +86,7 @@ EOT
[[ ! -d ${KLIPPER_CONFIG} ]] && mkdir -p "${KLIPPER_CONFIG}" [[ ! -d ${KLIPPER_CONFIG} ]] && mkdir -p "${KLIPPER_CONFIG}"
if [[ ! -f "${KLIPPER_CONFIG}/webcam.txt" ]]; then if [[ ! -f "${KLIPPER_CONFIG}/webcam.txt" ]]; then
status_msg "Creating webcam.txt config file ..." status_msg "Creating webcam.txt config file ..."
wget "${webcam_txt}" -O "${KLIPPER_CONFIG}/webcam.txt" cp "${webcam_txt}" "${KLIPPER_CONFIG}/webcam.txt"
ok_msg "Done!" ok_msg "Done!"
fi fi
@@ -190,4 +190,4 @@ function remove_mjpg-streamer() {
[[ -L "${KLIPPER_LOGS}/webcamd.log" ]] && rm -f "${KLIPPER_LOGS}/webcamd.log" [[ -L "${KLIPPER_LOGS}/webcamd.log" ]] && rm -f "${KLIPPER_LOGS}/webcamd.log"
print_confirm "MJPG-Streamer successfully removed!" print_confirm "MJPG-Streamer successfully removed!"
} }

247
scripts/mobileraker.sh Normal file
View File

@@ -0,0 +1,247 @@
#!/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 #
#=======================================================================#
#
# This file is written and maintained by Patrick Schmidt author of Mobileraker
# It is based of the kliperscreen.sh install script!
set -e
#===================================================#
#========== INSTALL MOBILERAKER COMPANION ==========#
#===================================================#
function mobileraker_systemd() {
local services
services=$(find "${SYSTEMD}" -maxdepth 1 -regextype posix-extended -regex "${SYSTEMD}/mobileraker.service")
echo "${services}"
}
function install_mobileraker() {
### return early if python version check fails
if [[ $(python3_check) == "false" ]]; then
local error="Versioncheck failed! Python 3.7 or newer required!\n"
error="${error} Please upgrade Python."
print_error "${error}" && return
fi
### first, we create a backup of the full klipper_config dir - safety first!
backup_config_dir
### install Mobileraker's Companion
mobileraker_setup
### add Mobileraker's Companion to the update manager in moonraker.conf
patch_mobileraker_update_manager
do_action_service "restart" "mobileraker"
}
function mobileraker_setup() {
local dep=(wget curl unzip dfu-util)
dependency_check "${dep[@]}"
status_msg "Cloning Mobileraker's companion from ${MOBILERAKER_REPO} ..."
# force remove existing Mobileraker's companion dir
[[ -d ${MOBILERAKER_DIR} ]] && rm -rf "${MOBILERAKER_DIR}"
# clone into fresh Mobileraker's companion dir
cd "${HOME}" || exit 1
if ! git clone "${MOBILERAKER_REPO}" "${MOBILERAKER_DIR}"; then
print_error "Cloning mobileraker's companion from\n ${MOBILERAKER_REPO}\n failed!"
exit 1
fi
status_msg "Installing Mobileraker's companion ..."
if "${MOBILERAKER_DIR}"/scripts/install-mobileraker-companion.sh; then
ok_msg "Mobileraker's companion successfully installed!"
else
print_error "Mobileraker's companion installation failed!"
exit 1
fi
}
#===================================================#
#=========== REMOVE MOBILERAKER COMPANION ==========#
#===================================================#
function remove_mobileraker() {
### remove Mobileraker's companion dir
if [[ -d ${MOBILERAKER_DIR} ]]; then
status_msg "Removing Mobileraker's companion directory ..."
rm -rf "${MOBILERAKER_DIR}" && ok_msg "Directory removed!"
fi
### remove Mobileraker's companion VENV dir
if [[ -d ${MOBILERAKER_ENV} ]]; then
status_msg "Removing Mobileraker's companion VENV directory ..."
rm -rf "${MOBILERAKER_ENV}" && ok_msg "Directory removed!"
fi
### remove Mobileraker's companion service
if [[ -e "${SYSTEMD}/mobileraker.service" ]]; then
status_msg "Removing mobileraker service ..."
do_action_service "stop" "mobileraker"
do_action_service "disable" "mobileraker"
sudo rm -f "${SYSTEMD}/mobileraker.service"
###reloading units
sudo systemctl daemon-reload
sudo systemctl reset-failed
ok_msg "Mobileraker's companion Service removed!"
fi
remove_mobileraker_logs
print_confirm "Mobileraker's companion successfully removed!"
}
function remove_mobileraker_logs() {
local files regex="${HOME//\//\\/}\/([A-Za-z0-9_]+)\/logs\/mobileraker\.log.*"
files=$(find "${HOME}" -maxdepth 3 -regextype posix-extended -regex "${regex}" | sort)
if [[ -n ${files} ]]; then
for file in ${files}; do
status_msg "Removing ${file} ..."
rm -f "${file}"
ok_msg "${file} removed!"
done
fi
}
#===================================================#
#=========== UPDATE MOBILERAKER COMPANION ==========#
#===================================================#
function update_mobileraker() {
local old_md5
old_md5=$(md5sum "${MOBILERAKER_DIR}/scripts/mobileraker-requirements.txt" | cut -d " " -f1)
do_action_service "stop" "mobileraker"
cd "${MOBILERAKER_DIR}"
git pull origin main -q && ok_msg "Fetch successfull!"
git checkout -f main && ok_msg "Checkout successfull"
if [[ $(md5sum "${MOBILERAKER_DIR}/scripts/mobileraker-requirements.txt" | cut -d " " -f1) != "${old_md5}" ]]; then
status_msg "New dependecies detected..."
"${MOBILERAKER_ENV}"/bin/pip install -r "${MOBILERAKER_DIR}/scripts/mobileraker-requirements.txt"
ok_msg "Dependencies have been installed!"
fi
ok_msg "Update complete!"
do_action_service "start" "mobileraker"
}
#===================================================#
#=========== MOBILERAKER COMPANION STATUS ==========#
#===================================================#
function get_mobileraker_status() {
local sf_count status
sf_count="$(mobileraker_systemd | wc -w)"
### remove the "SERVICE" entry from the data array if a moonraker service is installed
local data_arr=(SERVICE "${MOBILERAKER_DIR}" "${MOBILERAKER_ENV}")
(( sf_count > 0 )) && unset "data_arr[0]"
### count+1 for each found data-item from array
local filecount=0
for data in "${data_arr[@]}"; do
[[ -e ${data} ]] && filecount=$(( filecount + 1 ))
done
if (( filecount == ${#data_arr[*]} )); then
status="Installed!"
elif (( filecount == 0 )); then
status="Not installed!"
else
status="Incomplete!"
fi
echo "${status}"
}
function get_local_mobileraker_commit() {
[[ ! -d ${MOBILERAKER_DIR} || ! -d "${MOBILERAKER_DIR}/.git" ]] && return
local commit
cd "${MOBILERAKER_DIR}"
commit="$(git describe HEAD --always --tags | cut -d "-" -f 1,2)"
echo "${commit}"
}
function get_remote_mobileraker_commit() {
[[ ! -d ${MOBILERAKER_DIR} || ! -d "${MOBILERAKER_DIR}/.git" ]] && return
local commit
cd "${MOBILERAKER_DIR}" && git fetch origin -q
commit=$(git describe origin/main --always --tags | cut -d "-" -f 1,2)
echo "${commit}"
}
function compare_mobileraker_versions() {
local versions local_ver remote_ver
local_ver="$(get_local_mobileraker_commit)"
remote_ver="$(get_remote_mobileraker_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 "mobileraker"
else
versions="${green}$(printf " %-14s" "${local_ver}")${white}"
versions+="|${green}$(printf " %-13s" "${remote_ver}")${white}"
fi
echo "${versions}"
}
#================================================#
#=================== HELPERS ====================#
#================================================#
function patch_mobileraker_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 mobileraker\]\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 Mobileraker's Companion update manager section to moonraker.conf
status_msg "Adding Mobileraker's Companion to update manager in file:\n ${conf}"
/bin/sh -c "cat >> ${conf}" << MOONRAKER_CONF
[update_manager mobileraker]
type: git_repo
path: ${HOME}/mobileraker_companion
origin: https://github.com/Clon1998/mobileraker_companion.git
primary_branch:main
managed_services: mobileraker
env: ${HOME}/mobileraker-env/bin/python
requirements: scripts/mobileraker-requirements.txt
install_script: scripts/install-mobileraker-companion.sh
MOONRAKER_CONF
fi
patched="true"
done
if [[ ${patched} == "true" ]]; then
do_action_service "restart" "moonraker"
fi
}

View File

@@ -1,10 +1,10 @@
#!/usr/bin/env bash #!/usr/bin/env bash
#=======================================================================# #=======================================================================#
# Copyright (C) 2020 - 2022 Dominik Willner <th33xitus@gmail.com> # # Copyright (C) 2020 - 2023 Dominik Willner <th33xitus@gmail.com> #
# # # #
# This file is part of KIAUH - Klipper Installation And Update Helper # # This file is part of KIAUH - Klipper Installation And Update Helper #
# https://github.com/th33xitus/kiauh # # https://github.com/dw-0/kiauh #
# # # #
# This file may be distributed under the terms of the GNU GPLv3 license # # This file may be distributed under the terms of the GNU GPLv3 license #
#=======================================================================# #=======================================================================#
@@ -33,7 +33,7 @@ function telegram_bot_setup_dialog() {
status_msg "Initializing Telegram Bot installation ..." status_msg "Initializing Telegram Bot installation ..."
### first, we create a backup of the full klipper_config dir - safety first! ### first, we create a backup of the full klipper_config dir - safety first!
backup_klipper_config_dir backup_config_dir
local moonraker_count user_input=() moonraker_names=() local moonraker_count user_input=() moonraker_names=()
moonraker_count=$(echo "${moonraker_services}" | wc -w ) moonraker_count=$(echo "${moonraker_services}" | wc -w )
@@ -110,7 +110,7 @@ function telegram_bot_setup_dialog() {
} }
function install_telegram_bot_dependencies() { function install_telegram_bot_dependencies() {
local packages local packages log_name="Telegram Bot"
local install_script="${TELEGRAM_BOT_DIR}/scripts/install.sh" local install_script="${TELEGRAM_BOT_DIR}/scripts/install.sh"
### read PKGLIST from official install-script ### read PKGLIST from official install-script
@@ -121,21 +121,11 @@ function install_telegram_bot_dependencies() {
echo "${cyan}${packages}${white}" | tr '[:space:]' '\n' echo "${cyan}${packages}${white}" | tr '[:space:]' '\n'
read -r -a packages <<< "${packages}" read -r -a packages <<< "${packages}"
### Update system package info ### Update system package lists if stale
status_msg "Updating package lists..." update_system_package_lists
if ! sudo apt-get update --allow-releaseinfo-change; then
log_error "failure while updating package lists"
error_msg "Updating package lists failed!"
exit 1
fi
### Install required packages ### Install required packages
status_msg "Installing required packages..." install_system_packages "${log_name}" "packages[@]"
if ! sudo apt-get install --yes "${packages[@]}"; then
log_error "failure while installing required moonraker-telegram-bot packages"
error_msg "Installing required packages failed!"
exit 1
fi
} }
function create_telegram_bot_virtualenv() { function create_telegram_bot_virtualenv() {
@@ -203,29 +193,40 @@ function create_telegram_conf() {
local input=("${@}") local input=("${@}")
local telegram_bot_count=${input[0]} && unset "input[0]" local telegram_bot_count=${input[0]} && unset "input[0]"
local names=("${input[@]}") && unset "input[@]" local names=("${input[@]}") && unset "input[@]"
local log="${KLIPPER_LOGS}" local printer_data log_dir cfg cfg_dir
local cfg cfg_dir
if (( telegram_bot_count == 1 )); then if (( telegram_bot_count == 1 )); then
cfg_dir="${KLIPPER_CONFIG}" printer_data="${HOME}/printer_data"
log_dir="${printer_data}/logs"
cfg_dir="${printer_data}/config"
cfg="${cfg_dir}/telegram.conf" cfg="${cfg_dir}/telegram.conf"
### create required folder structure
create_required_folders "${printer_data}"
### write single instance config ### write single instance config
write_telegram_conf "${cfg_dir}" "${cfg}" "${log}" write_telegram_conf "${cfg_dir}" "${cfg}"
elif (( telegram_bot_count > 1 )); then elif (( telegram_bot_count > 1 )); then
local j=0 re="^[1-9][0-9]*$" local j=0 re="^[1-9][0-9]*$"
for (( i=1; i <= telegram_bot_count; i++ )); do for (( i=1; i <= telegram_bot_count; i++ )); do
### overwrite config folder if name is only a number
if [[ ${names[j]} =~ ${re} ]]; then printer_data="${HOME}/${names[${j}]}_data"
cfg_dir="${KLIPPER_CONFIG}/printer_${names[${j}]}" ### prefix instance name with "printer_" if it is only a number
else [[ ${names[j]} =~ ${re} ]] && printer_data="${HOME}/printer_${names[${j}]}_data"
cfg_dir="${KLIPPER_CONFIG}/${names[${j}]}"
fi
cfg_dir="${printer_data}/config"
cfg="${cfg_dir}/telegram.conf" cfg="${cfg_dir}/telegram.conf"
log_dir="${printer_data}/logs"
### create required folder structure
create_required_folders "${printer_data}"
### write multi instance config ### write multi instance config
write_telegram_conf "${cfg_dir}" "${cfg}" "${log}" write_telegram_conf "${cfg_dir}" "${cfg}"
j=$(( j + 1 )) j=$(( j + 1 ))
done && unset j done && unset j
@@ -235,17 +236,15 @@ function create_telegram_conf() {
} }
function write_telegram_conf() { function write_telegram_conf() {
local cfg_dir=${1} cfg=${2} log=${3} local cfg_dir=${1} cfg=${2}
local conf_template="${TELEGRAM_BOT_DIR}/scripts/base_install_template" local conf_template="${TELEGRAM_BOT_DIR}/scripts/base_install_template"
[[ ! -d ${cfg_dir} ]] && mkdir -p "${cfg_dir}"
if [[ ! -f ${cfg} ]]; then if [[ ! -f ${cfg} ]]; then
status_msg "Creating telegram.conf in ${cfg_dir} ..." status_msg "Creating telegram.conf in ${cfg_dir} ..."
cp "${conf_template}" "${cfg}" cp "${conf_template}" "${cfg}"
sed -i "s|some_log_path|${log}|g" "${cfg}"
ok_msg "telegram.conf created!" ok_msg "telegram.conf created!"
else else
status_msg "File '${cfg}' already exists!\nSkipping..." ok_msg "File '${cfg}' already exists! Skipping..."
fi fi
} }
@@ -253,16 +252,22 @@ function create_telegram_bot_service() {
local input=("${@}") local input=("${@}")
local instances=${input[0]} && unset "input[0]" local instances=${input[0]} && unset "input[0]"
local names=("${input[@]}") && unset "input[@]" local names=("${input[@]}") && unset "input[@]"
local cfg_dir cfg log service local printer_data cfg_dir cfg log service env_file
if (( instances == 1 )); then if (( instances == 1 )); then
cfg_dir="${KLIPPER_CONFIG}" printer_data="${HOME}/printer_data"
cfg_dir="${printer_data}/config"
cfg="${cfg_dir}/telegram.conf" cfg="${cfg_dir}/telegram.conf"
log="${KLIPPER_LOGS}/telegram.log" log="${printer_data}/logs/telegram.log"
service="${SYSTEMD}/moonraker-telegram-bot.service" service="${SYSTEMD}/moonraker-telegram-bot.service"
env_file="${printer_data}/systemd/moonraker-telegram-bot.env"
### create required folder structure
create_required_folders "${printer_data}"
### write single instance service ### write single instance service
write_telegram_bot_service "" "${cfg}" "${log}" "${service}" write_telegram_bot_service "" "${cfg}" "${log}" "${service}" "${env_file}"
ok_msg "Single Telegram Bot instance created!" ok_msg "Telegram Bot instance created!"
elif (( instances > 1 )); then elif (( instances > 1 )); then
local j=0 re="^[1-9][0-9]*$" local j=0 re="^[1-9][0-9]*$"
@@ -270,17 +275,27 @@ function create_telegram_bot_service() {
for (( i=1; i <= instances; i++ )); do for (( i=1; i <= instances; i++ )); do
### overwrite config folder if name is only a number ### overwrite config folder if name is only a number
if [[ ${names[j]} =~ ${re} ]]; then if [[ ${names[j]} =~ ${re} ]]; then
cfg_dir="${KLIPPER_CONFIG}/printer_${names[${j}]}" printer_data="${HOME}/printer_${names[${j}]}_data"
else else
cfg_dir="${KLIPPER_CONFIG}/${names[${j}]}" printer_data="${HOME}/${names[${j}]}_data"
fi fi
cfg_dir="${printer_data}/config"
cfg="${cfg_dir}/telegram.conf" cfg="${cfg_dir}/telegram.conf"
log="${KLIPPER_LOGS}/telegram-${names[${j}]}.log" log="${printer_data}/logs/telegram.log"
service="${SYSTEMD}/moonraker-telegram-bot-${names[${j}]}.service" service="${SYSTEMD}/moonraker-telegram-bot-${names[${j}]}.service"
env_file="${printer_data}/systemd/moonraker-telegram-bot.env"
### create required folder structure
create_required_folders "${printer_data}"
### write multi instance service ### write multi instance service
write_telegram_bot_service "${names[${j}]}" "${cfg}" "${log}" "${service}" if write_telegram_bot_service "${names[${j}]}" "${cfg}" "${log}" "${service}" "${env_file}"; then
ok_msg "Telegram Bot instance moonraker-telegram-bot-${names[${j}]} created!" ok_msg "Telegram Bot instance moonraker-telegram-bot-${names[${j}]} created!"
else
error_msg "An error occured during creation of instance moonraker-telegram-bot-${names[${j}]}!"
fi
j=$(( j + 1 )) j=$(( j + 1 ))
done && unset j done && unset j
@@ -290,17 +305,24 @@ function create_telegram_bot_service() {
} }
function write_telegram_bot_service() { function write_telegram_bot_service() {
local i=${1} cfg=${2} log=${3} service=${4} local i=${1} cfg=${2} log=${3} service=${4} env_file=${5}
local service_template="${KIAUH_SRCDIR}/resources/moonraker-telegram-bot.service" local service_template="${KIAUH_SRCDIR}/resources/moonraker-telegram-bot.service"
local env_template="${KIAUH_SRCDIR}/resources/moonraker-telegram-bot.env"
### replace all placeholders ### replace all placeholders
if [[ ! -f ${service} ]]; then if [[ ! -f ${service} ]]; then
status_msg "Creating Telegram Bot Service ${i} ..." status_msg "Creating service file for instance ${i} ..."
sudo cp "${service_template}" "${service}" sudo cp "${service_template}" "${service}"
[[ -z ${i} ]] && sudo sed -i "s|instance %INST% ||" "${service}" if [[ -z ${i} ]]; then
[[ -n ${i} ]] && sudo sed -i "s|%INST%|${i}|" "${service}" sudo sed -i "s| %INST%||" "${service}"
sudo sed -i "s|%USER%|${USER}|; s|%ENV%|${TELEGRAM_BOT_ENV}|; s|%DIR%|${TELEGRAM_BOT_DIR}|" "${service}" else
sudo sed -i "s|%CFG%|${cfg}|; s|%LOG%|${log}|" "${service}" sudo sed -i "s|%INST%|${i}|" "${service}"
fi
sudo sed -i "s|%USER%|${USER}|g; s|%TELEGRAM_BOT_DIR%|${TELEGRAM_BOT_DIR}|; s|%ENV%|${TELEGRAM_BOT_ENV}|; s|%ENV_FILE%|${env_file}|" "${service}"
status_msg "Creating environment file for instance ${i} ..."
cp "${env_template}" "${env_file}"
sed -i "s|%USER%|${USER}|; s|%TELEGRAM_BOT_DIR%|${TELEGRAM_BOT_DIR}|; s|%CFG%|${cfg}|; s|%LOG%|${log}|" "${env_file}"
fi fi
} }
@@ -343,9 +365,35 @@ function remove_telegram_bot_env() {
ok_msg "Directory removed!" ok_msg "Directory removed!"
} }
function remove_telegram_bot_env_file() {
local files regex="${HOME//\//\\/}\/([A-Za-z0-9_]+)\/systemd\/moonraker-telegram-bot\.env"
files=$(find "${HOME}" -maxdepth 3 -regextype posix-extended -regex "${regex}" | 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_telegram_bot_logs() { function remove_telegram_bot_logs() {
local files regex="${HOME//\//\\/}\/([A-Za-z0-9_]+)\/logs\/telegram\.log.*"
files=$(find "${HOME}" -maxdepth 3 -regextype posix-extended -regex "${regex}" | 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_telegram_bot_logs() {
local files regex="telegram(-[0-9a-zA-Z]+)?\.log(.*)?" local files regex="telegram(-[0-9a-zA-Z]+)?\.log(.*)?"
files=$(find "${KLIPPER_LOGS}" -maxdepth 1 -regextype posix-extended -regex "${KLIPPER_LOGS}/${regex}" | sort) files=$(find "${HOME}/klipper_logs" -maxdepth 1 -regextype posix-extended -regex "${HOME}/klipper_logs/${regex}" 2> /dev/null | sort)
if [[ -n ${files} ]]; then if [[ -n ${files} ]]; then
for file in ${files}; do for file in ${files}; do
@@ -360,7 +408,9 @@ function remove_telegram_bot() {
remove_telegram_bot_systemd remove_telegram_bot_systemd
remove_telegram_bot_dir remove_telegram_bot_dir
remove_telegram_bot_env remove_telegram_bot_env
remove_telegram_bot_env_file
remove_telegram_bot_logs remove_telegram_bot_logs
remove_legacy_telegram_bot_logs
local confirm="Moonraker-Telegram-Bot was successfully removed!" local confirm="Moonraker-Telegram-Bot was successfully removed!"
print_confirm "${confirm}" && return print_confirm "${confirm}" && return
@@ -436,7 +486,6 @@ function get_remote_telegram_bot_commit() {
} }
function compare_telegram_bot_versions() { function compare_telegram_bot_versions() {
unset MOONRAKER_TELEGRAM_BOT_UPDATE_AVAIL
local versions local_ver remote_ver local versions local_ver remote_ver
local_ver="$(get_local_telegram_bot_commit)" local_ver="$(get_local_telegram_bot_commit)"
remote_ver="$(get_remote_telegram_bot_commit)" remote_ver="$(get_remote_telegram_bot_commit)"
@@ -444,12 +493,11 @@ function compare_telegram_bot_versions() {
if [[ ${local_ver} != "${remote_ver}" ]]; then if [[ ${local_ver} != "${remote_ver}" ]]; then
versions="${yellow}$(printf " %-14s" "${local_ver}")${white}" versions="${yellow}$(printf " %-14s" "${local_ver}")${white}"
versions+="|${green}$(printf " %-13s" "${remote_ver}")${white}" versions+="|${green}$(printf " %-13s" "${remote_ver}")${white}"
# add moonraker-telegram-bot to the update all array for the update all function in the updater # add moonraker to application_updates_available in kiauh.ini
MOONRAKER_TELEGRAM_BOT_UPDATE_AVAIL="true" && update_arr+=(update_telegram_bot) add_to_application_updates "telegram_bot"
else else
versions="${green}$(printf " %-14s" "${local_ver}")${white}" versions="${green}$(printf " %-14s" "${local_ver}")${white}"
versions+="|${green}$(printf " %-13s" "${remote_ver}")${white}" versions+="|${green}$(printf " %-13s" "${remote_ver}")${white}"
MOONRAKER_TELEGRAM_BOT_UPDATE_AVAIL="false"
fi fi
echo "${versions}" echo "${versions}"
@@ -460,11 +508,13 @@ function compare_telegram_bot_versions() {
#================================================# #================================================#
function patch_telegram_bot_update_manager() { function patch_telegram_bot_update_manager() {
local moonraker_configs local patched moonraker_configs regex
moonraker_configs=$(find "${KLIPPER_CONFIG}" -type f -name "moonraker.conf" | sort) 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 for conf in ${moonraker_configs}; do
if ! grep -Eq "^\[update_manager moonraker-telegram-bot\]$" "${conf}"; then if ! grep -Eq "^\[update_manager moonraker-telegram-bot\]\s*$" "${conf}"; then
### add new line to conf if it doesn't end with one ### add new line to conf if it doesn't end with one
[[ $(tail -c1 "${conf}" | wc -l) -eq 0 ]] && echo "" >> "${conf}" [[ $(tail -c1 "${conf}" | wc -l) -eq 0 ]] && echo "" >> "${conf}"
@@ -482,5 +532,11 @@ install_script: scripts/install.sh
MOONRAKER_CONF MOONRAKER_CONF
fi fi
patched="true"
done done
if [[ ${patched} == "true" ]]; then
do_action_service "restart" "moonraker"
fi
} }

View File

@@ -1,10 +1,10 @@
#!/usr/bin/env bash #!/usr/bin/env bash
#=======================================================================# #=======================================================================#
# Copyright (C) 2020 - 2022 Dominik Willner <th33xitus@gmail.com> # # Copyright (C) 2020 - 2023 Dominik Willner <th33xitus@gmail.com> #
# # # #
# This file is part of KIAUH - Klipper Installation And Update Helper # # This file is part of KIAUH - Klipper Installation And Update Helper #
# https://github.com/th33xitus/kiauh # # https://github.com/dw-0/kiauh #
# # # #
# This file may be distributed under the terms of the GNU GPLv3 license # # This file may be distributed under the terms of the GNU GPLv3 license #
#=======================================================================# #=======================================================================#
@@ -15,9 +15,24 @@ set -e
#================ INSTALL MOONRAKER ================# #================ INSTALL MOONRAKER ================#
#===================================================# #===================================================#
###
# this function detects all installed moonraker
# systemd instances and returns their absolute path
function moonraker_systemd() { function moonraker_systemd() {
local services local services
services=$(find "${SYSTEMD}" -maxdepth 1 -regextype posix-extended -regex "${SYSTEMD}/moonraker(-[0-9a-zA-Z]+)?.service" | sort) local blacklist
local ignore
local match
###
# any moonraker client that uses "moonraker" in its own name must be blacklisted using
# this variable, otherwise they will be falsely recognized as moonraker instances
blacklist="obico"
ignore="${SYSTEMD}/moonraker-(${blacklist}).service"
match="${SYSTEMD}/moonraker(-[0-9a-zA-Z]+)?.service"
services=$(find "${SYSTEMD}" -maxdepth 1 -regextype awk ! -regex "${ignore}" -regex "${match}" | sort)
echo "${services}" echo "${services}"
} }
@@ -126,7 +141,7 @@ function moonraker_setup_dialog() {
} }
function install_moonraker_dependencies() { function install_moonraker_dependencies() {
local packages local packages log_name="Moonraker"
local install_script="${MOONRAKER_DIR}/scripts/install-moonraker.sh" local install_script="${MOONRAKER_DIR}/scripts/install-moonraker.sh"
### read PKGLIST from official install-script ### read PKGLIST from official install-script
@@ -137,21 +152,11 @@ function install_moonraker_dependencies() {
echo "${cyan}${packages}${white}" | tr '[:space:]' '\n' echo "${cyan}${packages}${white}" | tr '[:space:]' '\n'
read -r -a packages <<< "${packages}" read -r -a packages <<< "${packages}"
### Update system package info ### Update system package lists if stale
status_msg "Updating package lists..." update_system_package_lists
if ! sudo apt-get update --allow-releaseinfo-change; then
log_error "failure while updating package lists"
error_msg "Updating package lists failed!"
exit 1
fi
### Install required packages ### Install required packages
status_msg "Installing required packages..." install_system_packages "${log_name}" "packages[@]"
if ! sudo apt-get install --yes "${packages[@]}"; then
log_error "failure while installing required moonraker packages"
error_msg "Installing required packages failed!"
exit 1
fi
} }
function create_moonraker_virtualenv() { function create_moonraker_virtualenv() {
@@ -190,10 +195,10 @@ function moonraker_setup() {
create_moonraker_conf "${instance_arr[@]}" create_moonraker_conf "${instance_arr[@]}"
### step 4: create moonraker instances ### step 4: create moonraker instances
create_moonraker_service "${instance_arr[@]}" configure_moonraker_service "${instance_arr[@]}"
### step 5: create polkit rules for moonraker ### step 5: create polkit rules for moonraker
moonraker_polkit || true install_moonraker_polkit || true
### step 6: enable and start all instances ### step 6: enable and start all instances
do_action_service "enable" "moonraker" do_action_service "enable" "moonraker"
@@ -225,18 +230,19 @@ function create_moonraker_conf() {
local input=("${@}") local input=("${@}")
local moonraker_count=${input[0]} && unset "input[0]" local moonraker_count=${input[0]} && unset "input[0]"
local names=("${input[@]}") && unset "input[@]" local names=("${input[@]}") && unset "input[@]"
local log="${KLIPPER_LOGS}" local port lan printer_data cfg_dir cfg uds
local lan
port=7125
lan="$(hostname -I | cut -d" " -f1 | cut -d"." -f1-2).0.0/16" lan="$(hostname -I | cut -d" " -f1 | cut -d"." -f1-2).0.0/16"
local port=7125 cfg_dir cfg db uds
if (( moonraker_count == 1 )); then if (( moonraker_count == 1 )); then
cfg_dir="${KLIPPER_CONFIG}" printer_data="${HOME}/printer_data"
cfg_dir="${printer_data}/config"
cfg="${cfg_dir}/moonraker.conf" cfg="${cfg_dir}/moonraker.conf"
db="${HOME}/.moonraker_database" uds="${printer_data}/comms/klippy.sock"
uds="/tmp/klippy_uds"
### write single instance config ### write single instance config
write_moonraker_conf "${cfg_dir}" "${cfg}" "${port}" "${log}" "${db}" "${uds}" "${lan}" write_moonraker_conf "${cfg_dir}" "${cfg}" "${port}" "${uds}" "${lan}"
elif (( moonraker_count > 1 )); then elif (( moonraker_count > 1 )); then
local j=0 re="^[1-9][0-9]*$" local j=0 re="^[1-9][0-9]*$"
@@ -244,16 +250,17 @@ function create_moonraker_conf() {
for (( i=1; i <= moonraker_count; i++ )); do for (( i=1; i <= moonraker_count; i++ )); do
### overwrite config folder if name is only a number ### overwrite config folder if name is only a number
if [[ ${names[j]} =~ ${re} ]]; then if [[ ${names[j]} =~ ${re} ]]; then
cfg_dir="${KLIPPER_CONFIG}/printer_${names[${j}]}" printer_data="${HOME}/printer_${names[${j}]}_data"
else else
cfg_dir="${KLIPPER_CONFIG}/${names[${j}]}" printer_data="${HOME}/${names[${j}]}_data"
fi fi
cfg_dir="${printer_data}/config"
cfg="${cfg_dir}/moonraker.conf" cfg="${cfg_dir}/moonraker.conf"
uds="/tmp/klippy_uds-${names[${j}]}" uds="${printer_data}/comms/klippy.sock"
db="${HOME}/.moonraker_database_${names[${j}]}"
### write multi instance config ### write multi instance config
write_moonraker_conf "${cfg_dir}" "${cfg}" "${port}" "${log}" "${db}" "${uds}" "${lan}" write_moonraker_conf "${cfg_dir}" "${cfg}" "${port}" "${uds}" "${lan}"
port=$(( port + 1 )) port=$(( port + 1 ))
j=$(( j + 1 )) j=$(( j + 1 ))
done && unset j done && unset j
@@ -264,7 +271,7 @@ function create_moonraker_conf() {
} }
function write_moonraker_conf() { function write_moonraker_conf() {
local cfg_dir=${1} cfg=${2} port=${3} log=${4} db=${5} uds=${6} lan=${7} local cfg_dir=${1} cfg=${2} port=${3} uds=${4} lan=${5}
local conf_template="${KIAUH_SRCDIR}/resources/moonraker.conf" local conf_template="${KIAUH_SRCDIR}/resources/moonraker.conf"
[[ ! -d ${cfg_dir} ]] && mkdir -p "${cfg_dir}" [[ ! -d ${cfg_dir} ]] && mkdir -p "${cfg_dir}"
@@ -272,8 +279,7 @@ function write_moonraker_conf() {
if [[ ! -f ${cfg} ]]; then if [[ ! -f ${cfg} ]]; then
status_msg "Creating moonraker.conf in ${cfg_dir} ..." status_msg "Creating moonraker.conf in ${cfg_dir} ..."
cp "${conf_template}" "${cfg}" cp "${conf_template}" "${cfg}"
sed -i "s|%USER%|${USER}|g" "${cfg}" sed -i "s|%USER%|${USER}|g; s|%PORT%|${port}|; s|%UDS%|${uds}|" "${cfg}"
sed -i "s|%CFG%|${cfg_dir}|; s|%PORT%|${port}|; s|%LOG%|${log}|; s|%DB%|${db}|; s|%UDS%|${uds}|" "${cfg}"
# if host ip is not in the default ip ranges replace placeholder, # if host ip is not in the default ip ranges replace placeholder,
# otherwise remove placeholder from config # otherwise remove placeholder from config
if ! grep -q "${lan}" "${cfg}"; then if ! grep -q "${lan}" "${cfg}"; then
@@ -287,20 +293,24 @@ function write_moonraker_conf() {
fi fi
} }
function create_moonraker_service() { function configure_moonraker_service() {
local input=("${@}") local input=("${@}")
local moonraker_count=${input[0]} && unset "input[0]" local moonraker_count=${input[0]} && unset "input[0]"
local names=("${input[@]}") && unset "input[@]" local names=("${input[@]}") && unset "input[@]"
local cfg_dir cfg log service local printer_data cfg_dir service env_file
if (( moonraker_count == 1 )) && [[ ${#names[@]} -eq 0 ]]; then if (( moonraker_count == 1 )) && [[ ${#names[@]} -eq 0 ]]; then
i="" i=""
cfg_dir="${KLIPPER_CONFIG}" printer_data="${HOME}/printer_data"
cfg="${cfg_dir}/moonraker.conf" cfg_dir="${printer_data}/config"
log="${KLIPPER_LOGS}/moonraker.log"
service="${SYSTEMD}/moonraker.service" service="${SYSTEMD}/moonraker.service"
env_file="${printer_data}/systemd/moonraker.env"
### create required folder structure
create_required_folders "${printer_data}"
### write single instance service ### write single instance service
write_moonraker_service "" "${cfg}" "${log}" "${service}" write_moonraker_service "" "${printer_data}" "${service}" "${env_file}"
ok_msg "Moonraker instance created!" ok_msg "Moonraker instance created!"
elif (( moonraker_count > 1 )) && [[ ${#names[@]} -gt 0 ]]; then elif (( moonraker_count > 1 )) && [[ ${#names[@]} -gt 0 ]]; then
@@ -309,25 +319,27 @@ function create_moonraker_service() {
for (( i=1; i <= moonraker_count; i++ )); do for (( i=1; i <= moonraker_count; i++ )); do
### overwrite config folder if name is only a number ### overwrite config folder if name is only a number
if [[ ${names[j]} =~ ${re} ]]; then if [[ ${names[j]} =~ ${re} ]]; then
cfg_dir="${KLIPPER_CONFIG}/printer_${names[${j}]}" printer_data="${HOME}/printer_${names[${j}]}_data"
else else
cfg_dir="${KLIPPER_CONFIG}/${names[${j}]}" printer_data="${HOME}/${names[${j}]}_data"
fi fi
cfg="${cfg_dir}/moonraker.conf" cfg_dir="${printer_data}/config"
log="${KLIPPER_LOGS}/moonraker-${names[${j}]}.log"
service="${SYSTEMD}/moonraker-${names[${j}]}.service" service="${SYSTEMD}/moonraker-${names[${j}]}.service"
env_file="${printer_data}/systemd/moonraker.env"
### create required folder structure
create_required_folders "${printer_data}"
### write multi instance service ### write multi instance service
write_moonraker_service "${names[${j}]}" "${cfg}" "${log}" "${service}" write_moonraker_service "${names[${j}]}" "${printer_data}" "${service}" "${env_file}"
ok_msg "Moonraker instance 'moonraker-${names[${j}]}' created!" ok_msg "Moonraker instance 'moonraker-${names[${j}]}' created!"
j=$(( j + 1 )) j=$(( j + 1 ))
done && unset i done && unset i
### enable mainsails remoteMode if mainsail is found ### enable mainsails remoteMode if mainsail is found
if [[ -d ${MAINSAIL_DIR} ]]; then if [[ -d ${MAINSAIL_DIR} ]]; then
status_msg "Mainsail installation found! Enabling Mainsail remote mode ..."
enable_mainsail_remotemode enable_mainsail_remotemode
ok_msg "Mainsails remote mode enabled!"
fi fi
else else
@@ -336,18 +348,20 @@ function create_moonraker_service() {
} }
function write_moonraker_service() { function write_moonraker_service() {
local i=${1} cfg=${2} log=${3} service=${4} local i=${1} printer_data=${2} service=${3} env_file=${4}
local service_template="${KIAUH_SRCDIR}/resources/moonraker.service" local service_template="${KIAUH_SRCDIR}/resources/moonraker.service"
local env_template="${KIAUH_SRCDIR}/resources/moonraker.env"
### replace all placeholders ### replace all placeholders
if [[ ! -f ${service} ]]; then if [[ ! -f ${service} ]]; then
status_msg "Creating Moonraker Service ${i} ..." status_msg "Creating Moonraker Service ${i} ..."
sudo cp "${service_template}" "${service}" sudo cp "${service_template}" "${service}"
sudo cp "${env_template}" "${env_file}"
[[ -z ${i} ]] && sudo sed -i "s| for instance moonraker-%INST%||" "${service}" [[ -z ${i} ]] && sudo sed -i "s| %INST%||" "${service}"
[[ -n ${i} ]] && sudo sed -i "s|%INST%|${i}|" "${service}" [[ -n ${i} ]] && sudo sed -i "s|%INST%|${i}|" "${service}"
sudo sed -i "s|%USER%|${USER}|; s|%ENV%|${MOONRAKER_ENV}|; s|%DIR%|${MOONRAKER_DIR}|" "${service}" sudo sed -i "s|%USER%|${USER}|g; s|%MOONRAKER_DIR%|${MOONRAKER_DIR}|; s|%ENV%|${MOONRAKER_ENV}|; s|%ENV_FILE%|${env_file}|" "${service}"
sudo sed -i "s|%CFG%|${cfg}|; s|%LOG%|${log}|" "${service}" sudo sed -i "s|%USER%|${USER}|; s|%MOONRAKER_DIR%|${MOONRAKER_DIR}|; s|%PRINTER_DATA%|${printer_data}|" "${env_file}"
fi fi
} }
@@ -364,8 +378,20 @@ function print_mr_ip_list() {
### introduced due to ### introduced due to
### https://github.com/Arksine/moonraker/issues/349 ### https://github.com/Arksine/moonraker/issues/349
### https://github.com/Arksine/moonraker/pull/346 ### https://github.com/Arksine/moonraker/pull/346
function moonraker_polkit() { function install_moonraker_polkit() {
local POLKIT_LEGACY_FILE="/etc/polkit-1/localauthority/50-local.d/10-moonraker.pkla"
local POLKIT_FILE="/etc/polkit-1/rules.d/moonraker.rules"
local POLKIT_USR_FILE="/usr/share/polkit-1/rules.d/moonraker.rules"
local legacy_file_exists
local file_exists
local usr_file_exists
local has_sup local has_sup
local require_daemon_reload="false"
legacy_file_exists=$(sudo find "${POLKIT_LEGACY_FILE}" 2> /dev/null)
file_exists=$(sudo find "${POLKIT_FILE}" 2> /dev/null)
usr_file_exists=$(sudo find "${POLKIT_USR_FILE}" 2> /dev/null)
### check for required SupplementaryGroups entry in service files ### check for required SupplementaryGroups entry in service files
### write it to the service if it doesn't exist ### write it to the service if it doesn't exist
@@ -374,14 +400,25 @@ function moonraker_polkit() {
if [[ -z ${has_sup} ]]; then if [[ -z ${has_sup} ]]; then
status_msg "Adding moonraker-admin supplementary group to ${service} ..." status_msg "Adding moonraker-admin supplementary group to ${service} ..."
sudo sed -i "/^Type=simple$/a SupplementaryGroups=moonraker-admin" "${service}" sudo sed -i "/^Type=simple$/a SupplementaryGroups=moonraker-admin" "${service}"
require_daemon_reload="true"
ok_msg "Adding moonraker-admin supplementary group successfull!" ok_msg "Adding moonraker-admin supplementary group successfull!"
fi fi
done done
[[ -z ${has_sup} ]] && echo "reloading services!!!" && sudo systemctl daemon-reload if [[ ${require_daemon_reload} == "true" ]]; then
status_msg "Reloading unit files ..."
sudo systemctl daemon-reload
ok_msg "Unit files reloaded!"
fi
### execute moonrakers policykit-rules script ### execute moonrakers policykit-rules script only if rule files do not already exist
"${HOME}"/moonraker/scripts/set-policykit-rules.sh if [[ -z ${legacy_file_exists} && ( -z ${file_exists} || -z ${usr_file_exists} ) ]]; then
status_msg "Installing Moonraker policykit rules ..."
"${HOME}"/moonraker/scripts/set-policykit-rules.sh
ok_msg "Moonraker policykit rules installed!"
fi
return
} }
#==================================================# #==================================================#
@@ -417,9 +454,35 @@ function remove_moonraker_systemd() {
ok_msg "Moonraker Services removed!" ok_msg "Moonraker Services removed!"
} }
function remove_moonraker_env_file() {
local files regex="${HOME//\//\\/}\/([A-Za-z0-9_]+)\/systemd\/moonraker\.env"
files=$(find "${HOME}" -maxdepth 3 -regextype posix-extended -regex "${regex}" | 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_moonraker_logs() { function remove_moonraker_logs() {
local files regex="${HOME//\//\\/}\/([A-Za-z0-9_]+)\/logs\/moonraker\.log.*"
files=$(find "${HOME}" -maxdepth 3 -regextype posix-extended -regex "${regex}" | 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_moonraker_logs() {
local files regex="moonraker(-[0-9a-zA-Z]+)?\.log(.*)?" local files regex="moonraker(-[0-9a-zA-Z]+)?\.log(.*)?"
files=$(find "${KLIPPER_LOGS}" -maxdepth 1 -regextype posix-extended -regex "${KLIPPER_LOGS}/${regex}" 2> /dev/null | sort) files=$(find "${HOME}/klipper_logs" -maxdepth 1 -regextype posix-extended -regex "${HOME}/klipper_logs/${regex}" 2> /dev/null | sort)
if [[ -n ${files} ]]; then if [[ -n ${files} ]]; then
for file in ${files}; do for file in ${files}; do
@@ -473,7 +536,9 @@ function remove_moonraker_polkit() {
function remove_moonraker() { function remove_moonraker() {
remove_moonraker_sysvinit remove_moonraker_sysvinit
remove_moonraker_systemd remove_moonraker_systemd
remove_moonraker_env_file
remove_moonraker_logs remove_moonraker_logs
remove_legacy_moonraker_logs
remove_moonraker_api_key remove_moonraker_api_key
remove_moonraker_polkit remove_moonraker_polkit
remove_moonraker_dir remove_moonraker_dir
@@ -503,7 +568,7 @@ function update_moonraker() {
fi fi
### required due to https://github.com/Arksine/moonraker/issues/349 ### required due to https://github.com/Arksine/moonraker/issues/349
moonraker_polkit install_moonraker_polkit || true
ok_msg "Update complete!" ok_msg "Update complete!"
do_action_service "restart" "moonraker" do_action_service "restart" "moonraker"
@@ -557,7 +622,6 @@ function get_remote_moonraker_commit() {
} }
function compare_moonraker_versions() { function compare_moonraker_versions() {
unset MOONRAKER_UPDATE_AVAIL
local versions local_ver remote_ver local versions local_ver remote_ver
local_ver="$(get_local_moonraker_commit)" local_ver="$(get_local_moonraker_commit)"
remote_ver="$(get_remote_moonraker_commit)" remote_ver="$(get_remote_moonraker_commit)"
@@ -565,13 +629,12 @@ function compare_moonraker_versions() {
if [[ ${local_ver} != "${remote_ver}" ]]; then if [[ ${local_ver} != "${remote_ver}" ]]; then
versions="${yellow}$(printf " %-14s" "${local_ver}")${white}" versions="${yellow}$(printf " %-14s" "${local_ver}")${white}"
versions+="|${green}$(printf " %-13s" "${remote_ver}")${white}" versions+="|${green}$(printf " %-13s" "${remote_ver}")${white}"
# add moonraker to the update all array for the update all function in the updater # add moonraker to application_updates_available in kiauh.ini
MOONRAKER_UPDATE_AVAIL="true" && update_arr+=(update_moonraker) add_to_application_updates "moonraker"
else else
versions="${green}$(printf " %-14s" "${local_ver}")${white}" versions="${green}$(printf " %-14s" "${local_ver}")${white}"
versions+="|${green}$(printf " %-13s" "${remote_ver}")${white}" versions+="|${green}$(printf " %-13s" "${remote_ver}")${white}"
MOONRAKER_UPDATE_AVAIL="false"
fi fi
echo "${versions}" echo "${versions}"
} }

View File

@@ -1,10 +1,10 @@
#!/usr/bin/env bash #!/usr/bin/env bash
#=======================================================================# #=======================================================================#
# Copyright (C) 2020 - 2022 Dominik Willner <th33xitus@gmail.com> # # Copyright (C) 2020 - 2023 Dominik Willner <th33xitus@gmail.com> #
# # # #
# This file is part of KIAUH - Klipper Installation And Update Helper # # This file is part of KIAUH - Klipper Installation And Update Helper #
# https://github.com/th33xitus/kiauh # # https://github.com/dw-0/kiauh #
# # # #
# This file may be distributed under the terms of the GNU GPLv3 license # # This file may be distributed under the terms of the GNU GPLv3 license #
#=======================================================================# #=======================================================================#
@@ -74,23 +74,29 @@ function set_upstream_nginx_cfg() {
} }
function symlink_webui_nginx_log() { function symlink_webui_nginx_log() {
local interface=${1} path="${KLIPPER_LOGS}" local interface path access_log error_log regex logpaths
local access_log="/var/log/nginx/${interface}-access.log"
local error_log="/var/log/nginx/${interface}-error.log"
[[ ! -d ${path} ]] && mkdir -p "${path}" interface=${1}
access_log="/var/log/nginx/${interface}-access.log"
error_log="/var/log/nginx/${interface}-error.log"
regex="${HOME//\//\\/}\/([A-Za-z0-9_]+)\/logs"
logpaths=$(find "${HOME}" -maxdepth 2 -type d -regextype posix-extended -regex "${regex}" | sort)
if [[ -f ${access_log} && ! -L "${path}/${interface}-access.log" ]]; then for path in ${logpaths}; do
status_msg "Creating symlink for ${access_log} ..." [[ ! -d ${path} ]] && mkdir -p "${path}"
ln -s "${access_log}" "${path}"
ok_msg "Done!"
fi
if [[ -f ${error_log} && ! -L "${path}/${interface}-error.log" ]]; then if [[ -f ${access_log} && ! -L "${path}/${interface}-access.log" ]]; then
status_msg "Creating symlink for ${error_log} ..." status_msg "Creating symlink for ${access_log} ..."
ln -s "${error_log}" "${path}" ln -s "${access_log}" "${path}"
ok_msg "Done!" ok_msg "Symlink created: ${path}/${interface}-access.log"
fi fi
if [[ -f ${error_log} && ! -L "${path}/${interface}-error.log" ]]; then
status_msg "Creating symlink for ${error_log} ..."
ln -s "${error_log}" "${path}"
ok_msg "Symlink created: ${path}/${interface}-error.log"
fi
done
} }
function match_nginx_configs() { function match_nginx_configs() {
@@ -100,7 +106,9 @@ function match_nginx_configs() {
local common_vars="${NGINX_CONFD}/common_vars.conf" local common_vars="${NGINX_CONFD}/common_vars.conf"
local mainsail_nginx_cfg="/etc/nginx/sites-available/mainsail" local mainsail_nginx_cfg="/etc/nginx/sites-available/mainsail"
local fluidd_nginx_cfg="/etc/nginx/sites-available/fluidd" local fluidd_nginx_cfg="/etc/nginx/sites-available/fluidd"
local upstreams_webcams mainsail_webcams fluidd_webcams local upstreams_webcams
local mainsail_webcams
local fluidd_webcams
### reinstall nginx configs if the amount of upstreams don't match anymore ### reinstall nginx configs if the amount of upstreams don't match anymore
upstreams_webcams=$(grep -Ec "mjpgstreamer" "/etc/nginx/conf.d/upstreams.conf") upstreams_webcams=$(grep -Ec "mjpgstreamer" "/etc/nginx/conf.d/upstreams.conf")
@@ -124,7 +132,7 @@ function match_nginx_configs() {
status_msg "Outdated Mainsail config found! Updating ..." status_msg "Outdated Mainsail config found! Updating ..."
sudo rm -f "${mainsail_nginx_cfg}" sudo rm -f "${mainsail_nginx_cfg}"
sudo cp "${RESOURCES}/klipper_webui_nginx.cfg" "${mainsail_nginx_cfg}" sudo cp "${RESOURCES}/mainsail" "${mainsail_nginx_cfg}"
sudo sed -i "s/<<UI>>/mainsail/g" "${mainsail_nginx_cfg}" sudo sed -i "s/<<UI>>/mainsail/g" "${mainsail_nginx_cfg}"
sudo sed -i "/root/s/pi/${USER}/" "${mainsail_nginx_cfg}" sudo sed -i "/root/s/pi/${USER}/" "${mainsail_nginx_cfg}"
sudo sed -i "s/listen\s[0-9]*;/listen ${mainsail_port};/" "${mainsail_nginx_cfg}" sudo sed -i "s/listen\s[0-9]*;/listen ${mainsail_port};/" "${mainsail_nginx_cfg}"
@@ -138,7 +146,7 @@ function match_nginx_configs() {
status_msg "Outdated Fluidd config found! Updating ..." status_msg "Outdated Fluidd config found! Updating ..."
sudo rm -f "${fluidd_nginx_cfg}" sudo rm -f "${fluidd_nginx_cfg}"
sudo cp "${RESOURCES}/klipper_webui_nginx.cfg" "${fluidd_nginx_cfg}" sudo cp "${RESOURCES}/fluidd" "${fluidd_nginx_cfg}"
sudo sed -i "s/<<UI>>/fluidd/g" "${fluidd_nginx_cfg}" sudo sed -i "s/<<UI>>/fluidd/g" "${fluidd_nginx_cfg}"
sudo sed -i "/root/s/pi/${USER}/" "${fluidd_nginx_cfg}" sudo sed -i "/root/s/pi/${USER}/" "${fluidd_nginx_cfg}"
sudo sed -i "s/listen\s[0-9]*;/listen ${fluidd_port};/" "${fluidd_nginx_cfg}" sudo sed -i "s/listen\s[0-9]*;/listen ${fluidd_port};/" "${fluidd_nginx_cfg}"
@@ -260,8 +268,7 @@ function detect_conflicting_packages() {
echo -e "###### > Skip" echo -e "###### > Skip"
break;; break;;
*) *)
print_unkown_cmd error_msg "Invalid command!";;
print_msg && clear_msg;;
esac esac
done done
fi fi
@@ -269,34 +276,25 @@ function detect_conflicting_packages() {
function set_nginx_cfg() { function set_nginx_cfg() {
local interface=${1} local interface=${1}
if [[ ${SET_NGINX_CFG} == "true" ]]; then if [[ ${SET_NGINX_CFG} == "true" ]]; then
local cfg="${RESOURCES}/${interface}"
#check for dependencies #check for dependencies
local dep=(nginx) local dep=(nginx)
dependency_check "${dep[@]}" dependency_check "${dep[@]}"
status_msg "Creating Nginx configuration for ${interface^} ..." local cfg_src="${RESOURCES}/${interface}"
cat "${RESOURCES}/klipper_webui_nginx.cfg" > "${cfg}" local cfg_dest="/etc/nginx/sites-available/${interface}"
sed -i "s/<<UI>>/${interface}/g" "${cfg}"
status_msg "Creating NGINX configuration for ${interface^} ..."
# copy config to destination and set correct username
[[ -f ${cfg_dest} ]] && sudo rm -f "${cfg_dest}"
sudo cp "${cfg_src}" "${cfg_dest}"
sudo sed -i "/root/s/pi/${USER}/" "${cfg_dest}"
if [[ ${SET_LISTEN_PORT} != "${DEFAULT_PORT}" ]]; then if [[ ${SET_LISTEN_PORT} != "${DEFAULT_PORT}" ]]; then
status_msg "Configuring port for ${interface^} ..." sudo sed -i "s/listen\s[0-9]*;/listen ${SET_LISTEN_PORT};/" "${cfg_dest}"
sed -i "s/listen\s[0-9]*;/listen ${SET_LISTEN_PORT};/" "${cfg}" sudo sed -i "s/listen\s\[\:*\]\:[0-9]*;/listen \[::\]\:${SET_LISTEN_PORT};/" "${cfg_dest}"
sed -i "s/listen\s\[\:*\]\:[0-9]*;/listen \[::\]\:${SET_LISTEN_PORT};/" "${cfg}"
fi
#set correct user
if [[ ${interface} == "mainsail" || ${interface} == "fluidd" ]]; then
sudo sed -i "/root/s/pi/${USER}/" "${cfg}"
fi
#moving the config file into correct directory
sudo mv "${cfg}" "/etc/nginx/sites-available/${interface}"
ok_msg "Nginx configuration for ${interface^} was set!"
if [[ -n ${SET_LISTEN_PORT} ]]; then
ok_msg "${interface^} configured for port ${SET_LISTEN_PORT}!"
else
ok_msg "${interface^} configured for default port ${DEFAULT_PORT}!"
fi fi
#remove nginx default config #remove nginx default config
@@ -308,17 +306,32 @@ function set_nginx_cfg() {
if [[ ! -e "/etc/nginx/sites-enabled/${interface}" ]]; then if [[ ! -e "/etc/nginx/sites-enabled/${interface}" ]]; then
sudo ln -s "/etc/nginx/sites-available/${interface}" "/etc/nginx/sites-enabled/" sudo ln -s "/etc/nginx/sites-available/${interface}" "/etc/nginx/sites-enabled/"
fi fi
if [[ -n ${SET_LISTEN_PORT} ]]; then
ok_msg "${interface^} configured for port ${SET_LISTEN_PORT}!"
else
ok_msg "${interface^} configured for default port ${DEFAULT_PORT}!"
fi
sudo systemctl restart nginx.service sudo systemctl restart nginx.service
ok_msg "NGINX configuration for ${interface^} was set!"
fi fi
} }
###
# check if permissions of the users home directory
# grant execution rights to group and other which is
# required for NGINX to be able to serve Mainsail/Fluidd
#
function set_nginx_permissions() { function set_nginx_permissions() {
local distro_name version_id local homedir_perm
local exec_perms_count
distro_name=$(grep -E "^NAME=" /etc/os-release | cut -d'"' -f2) homedir_perm=$(ls -ld "${HOME}")
version_id=$(grep -E "^VERSION_ID=" /etc/os-release | cut -d'"' -f2) exec_perms_count=$(echo "${homedir_perm}" | cut -d" " -f1 | grep -c "x")
if [[ ${distro_name} == "Ubuntu" && ( ${version_id} == "21.10" || ${version_id} == "22.04") ]]; then if (( exec_perms_count < 3 )); then
status_msg "Granting NGINX the required permissions ..." status_msg "Granting NGINX the required permissions ..."
chmod og+x "${HOME}" && ok_msg "Done!" chmod og+x "${HOME}" && ok_msg "Done!"
fi fi
@@ -344,4 +357,4 @@ function detect_enabled_sites() {
FLUIDD_PORT=$(read_listen_port "fluidd") FLUIDD_PORT=$(read_listen_port "fluidd")
fi fi
} }

491
scripts/obico.sh Normal file
View File

@@ -0,0 +1,491 @@
#!/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 MOONRAKER-OBICO ============#
#===================================================#
function moonraker_obico_systemd() {
local services
services=$(find "${SYSTEMD}" -maxdepth 1 -regextype posix-extended -regex "${SYSTEMD}/moonraker-obico(-[0-9a-zA-Z]+)?.service")
echo "${services}"
}
function moonraker_obico_config() {
local moonraker_cfg_dirs
read -r -a moonraker_cfg_dirs <<< "$(get_instance_folder_path "config")"
if (( ${#moonraker_cfg_dirs[@]} > 0 )); then
echo "${moonraker_cfg_dirs[${1}]}/moonraker-obico.cfg"
else
echo ""
fi
}
function moonraker_obico_needs_linking() {
local moonraker_obico_cfg=${1}
if [[ ! -f "${moonraker_obico_cfg}" ]]; then
return 1
fi
if grep -s -E "^[^#]" "${moonraker_obico_cfg}" | grep -q 'auth_token'; then
return 1
else
return 0
fi
}
function obico_server_url_prompt() {
top_border
printf "|${green}%-55s${white}|\n" " Obico Server URL"
blank_line
echo -e "| You can use a self-hosted Obico Server or the Obico |"
echo -e "| Cloud. For more information, please visit: |"
echo -e "| https://obico.io. |"
blank_line
echo -e "| For the Obico Cloud, leave it as the default: |"
printf "|${cyan}%-55s${white}|\n" " https://app.obico.io"
blank_line
echo -e "| For self-hosted server, specify: |"
printf "|${cyan}%-55s${white}|\n" " http://server_ip:port"
echo -e "| For instance, 'http://192.168.0.5:3334'. |"
bottom_border
}
function moonraker_obico_setup_dialog() {
status_msg "Initializing Moonraker-obico installation ..."
local moonraker_count
local moonraker_names
moonraker_count=$(moonraker_systemd | wc -w)
if (( moonraker_count == 0 )); then
### return early if moonraker is not installed
local error="Moonraker not installed! Please install Moonraker first!"
log_error "Moonraker-obico setup started without Moonraker being installed. Aborting setup."
print_error "${error}" && return
elif (( moonraker_count > 1 )); then
# moonraker_names is valid only in case of multi-instance
read -r -a moonraker_names <<< "$(get_multi_instance_names)"
fi
local moonraker_obico_services
local existing_moonraker_obico_count
moonraker_obico_services=$(moonraker_obico_systemd)
existing_moonraker_obico_count=$(echo "${moonraker_obico_services}" | wc -w )
local allowed_moonraker_obico_count=$(( moonraker_count - existing_moonraker_obico_count ))
# Allow user to reinstall an incomplete installation.
if (( allowed_moonraker_obico_count == 0 && moonraker_count > 0 )) && [[ $(get_moonraker_obico_status) != "Not linked!" ]]; then
local yn
while true; do
echo "${yellow}Obico for Klipper is already installed.${white}"
echo "It is safe to run the install again to repair any issues."
echo ""
local question="Do you want to reinstall Obico for Klipper?"
read -p "${cyan}###### ${question} (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 Obico for Klipper installation...\n"
return;;
*)
error_msg "Invalid Input!";;
esac
done
# The user responded yes, allow the install to run again.
allowed_moonraker_obico_count=1
fi
if (( allowed_moonraker_obico_count > 0 )); then
local new_moonraker_obico_count
### Step 1: Ask for the number of moonraker-obico instances to install
if (( moonraker_count == 1 )); then
ok_msg "Moonraker installation found!\n"
new_moonraker_obico_count=1
elif (( moonraker_count > 1 )); then
top_border
printf "|${green}%-55s${white}|\n" " ${moonraker_count} Moonraker instances found!"
for name in "${moonraker_names[@]}"; do
printf "|${cyan}%-57s${white}|\n" " ● moonraker-${name}"
done
blank_line
if (( existing_moonraker_obico_count > 0 )); then
printf "|${green}%-55s${white}|\n" " ${existing_moonraker_obico_count} Moonraker-obico instances already installed!"
for svc in ${moonraker_obico_services}; do
printf "|${cyan}%-57s${white}|\n" " ● moonraker-obco-$(get_instance_name "${svc}")"
done
fi
blank_line
echo -e "| The setup will apply the same names to |"
echo -e "| Moonraker-obico! |"
blank_line
echo -e "| Please select the number of Moonraker-obico instances |"
echo -e "| to install. Usually one Moonraker-obico instance per |"
echo -e "| Moonraker instance is required, but you may not |"
echo -e "| install more Moonraker-obico instances than available |"
echo -e "| Moonraker instances. |"
bottom_border
### ask for amount of instances
local re="^[1-9][0-9]*$"
while [[ ! ${new_moonraker_obico_count} =~ ${re} || ${new_moonraker_obico_count} -gt ${allowed_moonraker_obico_count} ]]; do
read -p "${cyan}###### Number of new Moonraker-obico instances to set up:${white} " -i "${allowed_moonraker_obico_count}" -e new_moonraker_obico_count
### break if input is valid
[[ ${new_moonraker_obico_count} =~ ${re} && ${new_moonraker_obico_count} -le ${allowed_moonraker_obico_count} ]] && break
### conditional error messages
[[ ! ${new_moonraker_obico_count} =~ ${re} ]] && error_msg "Input not a number"
(( new_moonraker_obico_count > allowed_moonraker_obico_count )) && error_msg "Number of Moonraker-obico instances larger than installed Moonraker instances"
done && select_msg "${new_moonraker_obico_count}"
else
log_error "Internal error. moonraker_count of '${moonraker_count}' not equal or grather than one!"
return 1
fi # (( moonraker_count == 1 ))
### Step 2: Confirm instance amount
local yn
while true; do
(( new_moonraker_obico_count == 1 )) && local question="Install Moonraker-obico?"
(( new_moonraker_obico_count > 1 )) && local question="Install ${new_moonraker_obico_count} Moonraker-obico instances?"
read -p "${cyan}###### ${question} (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 Moonraker-obico setup ...\n"
return;;
*)
error_msg "Invalid Input!";;
esac
done
fi # (( allowed_moonraker_obico_count > 0 ))
if (( new_moonraker_obico_count > 0 )); then
### Step 3: Ask for the Obico server URL
obico_server_url_prompt
local obico_server_url
while true; do
read -p "${cyan}###### Obico Server URL:${white} " -i "https://app.obico.io" -e obico_server_url
if echo "${obico_server_url}" | grep -qE "^(http|https)://[a-zA-Z0-9./?=_%:-]*"; then
break
else
error_msg "Invalid server URL!"
fi
done
(( new_moonraker_obico_count > 1 )) && status_msg "Installing ${new_moonraker_obico_count} Moonraker-obico instances ..."
(( new_moonraker_obico_count == 1 )) && status_msg "Installing Moonraker-obico ..."
### Step 5: Clone the moonraker-obico repo
clone_moonraker_obico "${MOONRAKER_OBICO_REPO}"
### step 6: call moonrake-obico/install.sh with the correct params
local port=7125
local instance_cfg_dirs
local instance_log_dirs
read -r -a instance_cfg_dirs <<< "$(get_instance_folder_path "config")"
read -r -a instance_log_dirs <<< "$(get_instance_folder_path "logs")"
if (( moonraker_count == 1 )); then
"${MOONRAKER_OBICO_DIR}/install.sh"\
-C "${instance_cfg_dirs[0]}/moonraker.conf"\
-p "${port}" -H 127.0.0.1 -l\
"${instance_log_dirs[0]}"\
-L -S "${obico_server_url}"
elif (( moonraker_count > 1 )); then
local j=${existing_moonraker_obico_count}
for (( i=1; i <= new_moonraker_obico_count; i++ )); do
"${MOONRAKER_OBICO_DIR}/install.sh"\
-n "${moonraker_names[${j}]}"\
-C "${instance_cfg_dirs[${j}]}/moonraker.conf"\
-p $((port+j))\
-H 127.0.0.1\
-l "${instance_log_dirs[${j}]}"\
-L -S "${obico_server_url}"
j=$(( j + 1 ))
done && unset j
fi # (( moonraker_count == 1 ))
fi # (( new_moonraker_obico_count > 0 ))
### Step 7: Link to the Obico server if necessary
local not_linked_instances=()
if (( moonraker_count == 1 )); then
if moonraker_obico_needs_linking "$(moonraker_obico_config 0)"; then
not_linked_instances+=("0")
fi
elif (( moonraker_count > 1 )); then
for (( i=0; i <= moonraker_count; i++ )); do
if moonraker_obico_needs_linking "$(moonraker_obico_config "${i}")"; then
not_linked_instances+=("${i}")
fi
done
fi # (( moonraker_count == 1 ))
if (( ${#not_linked_instances[@]} > 0 )); then
top_border
if (( moonraker_count == 1 )); then
printf "|${green}%-55s${white}|\n" " Moonraker-obico not linked to the server!"
else
printf "|${green}%-55s${white}|\n" " ${#not_linked_instances[@]} Moonraker-obico instances not linked to the server!"
for i in "${not_linked_instances[@]}"; do
printf "|${cyan}%-57s${white}|\n" " ● moonraker-obico-${moonraker_names[${i}]}"
done
fi
blank_line
echo -e "| To link to your Obico Server account, you need to |"
echo -e "| obtain the 6-digit verification code in the Obico |"
echo -e "| mobile or web app. For more information, visit: |"
echo -e "| https://www.obico.io/docs/user-guides/klipper-setup/ |"
blank_line
echo -e "| If you don't want to link the printer now, you can |"
echo -e "| restart the linking process later by: |"
echo -e "| 1. 'cd ~/kiauh && ./kiauh.sh' to launch KIAUH. |"
echo -e "| 2. Select ${green}[Install]${white} |"
echo -e "| 3. Select ${green}[Link to Obico Server]${white} |"
bottom_border
while true; do
read -p "${cyan}###### Link to your Obico Server account now? (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 Moonraker-obico setup ...\n"
return;;
*)
error_msg "Invalid Input!";;
esac
done
if (( moonraker_count == 1 )); then
status_msg "Link moonraker-obico to the Obico Server..."
"${MOONRAKER_OBICO_DIR}/scripts/link.sh" -q -c "$(moonraker_obico_config 0)"
elif (( moonraker_count > 1 )); then
for i in "${not_linked_instances[@]}"; do
local name="${moonraker_names[i]}"
status_msg "Link moonraker-obico-${name} to the Obico Server..."
"${MOONRAKER_OBICO_DIR}/scripts/link.sh" -q -n "${name}" -c "$(moonraker_obico_config "${i}")"
done
fi # (( moonraker_count == 1 ))
fi # (( ${#not_linked_instances[@]} > 0 ))
}
function clone_moonraker_obico() {
local repo=${1}
status_msg "Cloning Moonraker-obico from ${repo} ..."
### force remove existing Moonraker-obico dir
[[ -d "${MOONRAKER_OBICO_DIR}" ]] && rm -rf "${MOONRAKER_OBICO_DIR}"
cd "${HOME}" || exit 1
if ! git clone "${repo}" "${MOONRAKER_OBICO_DIR}"; then
print_error "Cloning Moonraker-obico from\n ${repo}\n failed!"
exit 1
fi
}
function moonraker_obico_install() {
"${MOONRAKER_OBICO_DIR}/install.sh" "$@"
}
#===================================================#
#============= REMOVE MOONRAKER-OBICO ==============#
#===================================================#
function remove_moonraker_obico_systemd() {
[[ -z $(moonraker_obico_systemd) ]] && return
status_msg "Removing Moonraker-obico Systemd Services ..."
for service in $(moonraker_obico_systemd | cut -d"/" -f5); do
status_msg "Removing ${service} ..."
sudo systemctl stop "${service}"
sudo systemctl disable "${service}"
sudo rm -f "${SYSTEMD}/${service}"
ok_msg "Done!"
done
### reloading units
sudo systemctl daemon-reload
sudo systemctl reset-failed
ok_msg "Moonraker-obico Services removed!"
}
function remove_moonraker_obico_logs() {
local files regex="${HOME//\//\\/}\/([A-Za-z0-9_]+)\/logs\/moonraker-obico(-[0-9a-zA-Z]+)?\.log(.*)?"
files=$(find "${HOME}" -maxdepth 3 -regextype posix-extended -regex "${regex}" | 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_moonraker_obico_logs() {
local files regex="moonraker-obico(-[0-9a-zA-Z]+)?\.log(.*)?"
files=$(find "${HOME}/klipper_logs" -maxdepth 1 -regextype posix-extended -regex "${HOME}/klipper_logs/${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_moonraker_obico_dir() {
[[ ! -d ${MOONRAKER_OBICO_DIR} ]] && return
status_msg "Removing Moonraker-obico directory ..."
rm -rf "${MOONRAKER_OBICO_DIR}"
ok_msg "Directory removed!"
}
function remove_moonraker_obico_env() {
[[ ! -d "${HOME}/moonraker-obico-env" ]] && return
status_msg "Removing moonraker-obico-env directory ..."
rm -rf "${HOME}/moonraker-obico-env"
ok_msg "Directory removed!"
}
function remove_moonraker_obico() {
remove_moonraker_obico_systemd
remove_moonraker_obico_logs
remove_moonraker_obico_dir
remove_moonraker_obico_env
print_confirm "Moonraker-obico was successfully removed!"
return
}
#===================================================#
#============= UPDATE MOONRAKER-OBICO ==============#
#===================================================#
function update_moonraker_obico() {
do_action_service "stop" "moonraker-obico"
if [[ ! -d ${MOONRAKER_OBICO_DIR} ]]; then
clone_moonraker_obico "${MOONRAKER_OBICO_REPO}"
else
status_msg "Updating Moonraker-obico ..."
cd "${MOONRAKER_OBICO_DIR}" && git pull
fi
"${MOONRAKER_OBICO_DIR}/install.sh" -U
ok_msg "Update complete!"
do_action_service "restart" "moonraker-obico"
}
#===================================================#
#============= MOONRAKER-OBICO STATUS ==============#
#===================================================#
function get_moonraker_obico_status() {
local status
local service_count
local is_linked
local moonraker_obico_services
moonraker_obico_services=$(moonraker_obico_systemd)
service_count=$(echo "${moonraker_obico_services}" | wc -w )
is_linked="true"
if [[ -n ${moonraker_obico_services} ]]; then
for cfg_dir in $(get_instance_folder_path "config"); do
if moonraker_obico_needs_linking "${cfg_dir}/moonraker-obico.cfg"; then
is_linked="false"
fi
done
fi
if (( service_count == 0 )); then
status="Not installed!"
elif [[ ! -d "${MOONRAKER_OBICO_DIR}" ]]; then
status="Incomplete!"
elif [[ ${is_linked} == "false" ]]; then
status="Not linked!"
else
status="Installed!"
fi
echo "${status}"
}
function get_local_moonraker_obico_commit() {
[[ ! -d ${MOONRAKER_OBICO_DIR} || ! -d "${MOONRAKER_OBICO_DIR}/.git" ]] && return
local commit
cd "${MOONRAKER_OBICO_DIR}"
commit="$(git describe HEAD --always --tags | cut -d "-" -f 1,2)"
echo "${commit}"
}
function get_remote_moonraker_obico_commit() {
[[ ! -d ${MOONRAKER_OBICO_DIR} || ! -d "${MOONRAKER_OBICO_DIR}/.git" ]] && return
local commit
cd "${MOONRAKER_OBICO_DIR}" && git fetch origin -q
commit=$(git describe origin/master --always --tags | cut -d "-" -f 1,2)
echo "${commit}"
}
function compare_moonraker_obico_versions() {
local versions local_ver remote_ver
local_ver="$(get_local_moonraker_obico_commit)"
remote_ver="$(get_remote_moonraker_obico_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 "moonraker_obico"
else
versions="${green}$(printf " %-14s" "${local_ver}")${white}"
versions+="|${green}$(printf " %-13s" "${remote_ver}")${white}"
fi
echo "${versions}"
}
###
# it is possible, that moonraker_obico is installed in a so called
# "non-linked" state. the linking can be achieved by running the
# installation script again. this function will check the obico
# installation status and returns the correctly formulated menu title
#
function obico_install_title() {
if [[ $(get_moonraker_obico_status) == "Not linked!" ]]; then
echo "[Link to Obico Server]"
else
echo "[Obico for Klipper] "
fi
}

385
scripts/octoeverywhere.sh Normal file
View File

@@ -0,0 +1,385 @@
#!/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 #
#=======================================================================#
#
# This file is written and maintained by Quinn Damerell from OctoEverywhere
# Please contact our support team if you need any help!
# https://octoeverywhere.com/support
#
set -e
#===================================================#
#============== Install ============#
#===================================================#
function octoeverywhere_systemd() {
local services
services=$(find "${SYSTEMD}" -maxdepth 1 -regextype posix-extended -regex "${SYSTEMD}/octoeverywhere(-[0-9a-zA-Z]+)?.service")
echo "${services}"
}
function octoeverywhere_setup_dialog() {
status_msg "Initializing OctoEverywhere for Klipper installation ..."
# First, check for moonraker service instances.
local moonraker_count
local moonraker_names
moonraker_count=$(moonraker_systemd | wc -w)
if (( moonraker_count == 0 )); then
### return early if moonraker is not installed
local error="Moonraker not installed! Please install Moonraker first!"
log_error "OctoEverywhere setup started without Moonraker being installed. Aborting setup."
print_error "${error}" && return
elif (( moonraker_count > 1 )); then
# moonraker_names is valid only in case of multi-instance
read -r -a moonraker_names <<< "$(get_multi_instance_names)"
fi
# Next, check for any existing OctoEverywhere services.
local octoeverywhere_services
local existing_octoeverywhere_count
octoeverywhere_services=$(octoeverywhere_systemd)
existing_octoeverywhere_count=$(echo "${octoeverywhere_services}" | wc -w )
# We need to make the moonraker instance count to the OctoEverywhere service count.
local allowed_octoeverywhere_count=$(( moonraker_count - existing_octoeverywhere_count ))
if (( allowed_octoeverywhere_count > 0 )); then
local new_octoeverywhere_count
### Step 1: Ask for the number of OctoEverywhere instances to install
if (( moonraker_count == 1 )); then
ok_msg "Moonraker installation found!\n"
new_octoeverywhere_count=1
elif (( moonraker_count > 1 )); then
top_border
printf "|${green}%-55s${white}|\n" " ${moonraker_count} Moonraker instances found!"
for name in "${moonraker_names[@]}"; do
printf "|${cyan}%-57s${white}|\n" " ● moonraker-${name}"
done
blank_line
if (( existing_octoeverywhere_count > 0 )); then
printf "|${green}%-55s${white}|\n" " ${existing_octoeverywhere_count} OctoEverywhere instances already installed!"
for svc in ${octoeverywhere_services}; do
printf "|${cyan}%-57s${white}|\n" " ● octoeverywhere-$(get_instance_name "${svc}")"
done
fi
blank_line
echo -e "| The setup will apply the same names to |"
echo -e "| OctoEverywhere |"
blank_line
echo -e "| Please select the number of OctoEverywhere instances |"
echo -e "| to install. Usually one OctoEverywhere instance per |"
echo -e "| Moonraker instance is required, but you may not |"
echo -e "| install more OctoEverywhere instances than available |"
echo -e "| Moonraker instances. |"
bottom_border
### ask for amount of instances
local re="^[1-9][0-9]*$"
while [[ ! ${new_octoeverywhere_count} =~ ${re} || ${new_octoeverywhere_count} -gt ${allowed_octoeverywhere_count} ]]; do
read -p "${cyan}###### Number of new OctoEverywhere instances to set up:${white} " -i "${allowed_octoeverywhere_count}" -e new_octoeverywhere_count
### break if input is valid
[[ ${new_octoeverywhere_count} =~ ${re} && ${new_octoeverywhere_count} -le ${allowed_octoeverywhere_count} ]] && break
### conditional error messages
[[ ! ${new_octoeverywhere_count} =~ ${re} ]] && error_msg "Input not a number"
(( new_octoeverywhere_count > allowed_octoeverywhere_count )) && error_msg "Number of OctoEverywhere instances larger than installed Moonraker instances"
done && select_msg "${new_octoeverywhere_count}"
else
log_error "Internal error. moonraker_count of '${moonraker_count}' not equal or grater than one!"
return 1
fi # (( moonraker_count == 1 ))
fi # (( allowed_octoeverywhere_count > 0 ))
# Special case for one moonraker instance with OctoEverywhere already installed.
# If the user selects the install option again, they might be trying to recover the install
# or complete a printer link they didn't finish in the past.
# So in this case, we will allow them to run the install script again, since it's safe to run
# if the service is already installed, it will repair any missing issues.
if (( allowed_octoeverywhere_count == 0 && moonraker_count == 1 )); then
local yn
while true; do
echo "${yellow}OctoEverywhere is already installed.${white}"
echo "It is safe to run the install again to repair any issues or if the printer isn't linked, run the printer linking logic again."
echo ""
local question="Do you want to run the OctoEverywhere recovery or linking logic again?"
read -p "${cyan}###### ${question} (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 OctoEverywhere setup ...\n"
return;;
*)
error_msg "Invalid Input!";;
esac
done
# The user responded yes, allow the install to run again.
allowed_octoeverywhere_count=1
fi
# If there's something to install, do it!
if (( allowed_octoeverywhere_count > 0 )); then
(( new_octoeverywhere_count > 1 )) && status_msg "Installing ${new_octoeverywhere_count} OctoEverywhere instances ..."
(( new_octoeverywhere_count == 1 )) && status_msg "Installing OctoEverywhere ..."
# Ensure the basic system dependencies are installed.
local dep=(git dfu-util virtualenv python3 python3-pip python3-venv)
dependency_check "${dep[@]}"
# Close the repo
clone_octoeverywhere "${OCTOEVERYWHERE_REPO}"
# Call install with the correct args.
local instance_cfg_dirs
read -r -a instance_cfg_dirs <<< "$(get_instance_folder_path "config")"
echo instance_cfg_dirs[0]
if (( moonraker_count == 1 )); then
"${OCTOEVERYWHERE_DIR}/install.sh" "${instance_cfg_dirs[0]}/moonraker.conf"
elif (( moonraker_count > 1 )); then
local j=${existing_octoeverywhere_count}
for (( i=1; i <= new_octoeverywhere_count; i++ )); do
"${OCTOEVERYWHERE_DIR}/install.sh" "${instance_cfg_dirs[${j}]}/moonraker.conf"
j=$(( j + 1 ))
done && unset j
fi # (( moonraker_count == 1 ))
fi # (( allowed_octoeverywhere_count > 0 ))
}
function clone_octoeverywhere() {
local repo=${1}
status_msg "Cloning OctoEverywhere..."
### force remove existing repos
[[ -d "${OCTOEVERYWHERE_DIR}" ]] && rm -rf "${OCTOEVERYWHERE_DIR}"
cd "${HOME}" || exit 1
if ! git clone "${repo}" "${OCTOEVERYWHERE_DIR}"; then
print_error "Cloning OctoEverywhere from\n ${repo}\n failed!"
exit 1
fi
}
function octoeverywhere_install() {
"${OCTOEVERYWHERE_DIR}/install.sh" "$@"
}
#===================================================#
#============= Remove ==============#
#===================================================#
function remove_octoeverywhere_systemd() {
[[ -z $(octoeverywhere_systemd) ]] && return
status_msg "Removing OctoEverywhere Systemd Services ..."
for service in $(octoeverywhere_systemd | cut -d"/" -f5); do
status_msg "Removing ${service} ..."
sudo systemctl stop "${service}"
sudo systemctl disable "${service}"
sudo rm -f "${SYSTEMD}/${service}"
ok_msg "Done!"
done
### reloading units
sudo systemctl daemon-reload
sudo systemctl reset-failed
ok_msg "OctoEverywhere Services removed!"
}
function remove_octoeverywhere_logs() {
local files regex="${HOME//\//\\/}\/([A-Za-z0-9_]+)\/logs\/octoeverywhere(-[0-9a-zA-Z]+)?\.log(.*)?"
files=$(find "${HOME}" -maxdepth 3 -regextype posix-extended -regex "${regex}" | 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_octoeverywhere_dir() {
[[ ! -d ${OCTOEVERYWHERE_DIR} ]] && return
status_msg "Removing OctoEverywhere directory ..."
rm -rf "${OCTOEVERYWHERE_DIR}"
ok_msg "Directory removed!"
}
function remove_octoeverywhere_config() {
# Remove the system config but not the main config, so the printer id doesn't get lost.
local files regex="${HOME//\//\\/}\/([A-Za-z0-9_]+)\/config\/octoeverywhere-system(-[0-9a-zA-Z]+)?\.cfg(.*)?"
files=$(find "${HOME}" -maxdepth 4 -regextype posix-extended -regex "${regex}" | 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_octoeverywhere_store_dir() {
local files regex="${HOME//\//\\/}\/([A-Za-z0-9_]+)\/octoeverywhere-store"
files=$(find "${HOME}" -maxdepth 2 -type d -regextype posix-extended -regex "${regex}" | sort)
if [[ -n ${files} ]]; then
for file in ${files}; do
status_msg "Removing ${file} ..."
rm -rf "${file}"
ok_msg "${file} removed!"
done
fi
}
function remove_octoeverywhere_env() {
[[ ! -d "${HOME}/octoeverywhere-env" ]] && return
status_msg "Removing octoeverywhere-env directory ..."
rm -rf "${HOME}/octoeverywhere-env"
ok_msg "Directory removed!"
}
function remove_octoeverywhere()
{
remove_octoeverywhere_systemd
remove_octoeverywhere_logs
remove_octoeverywhere_dir
remove_octoeverywhere_env
remove_octoeverywhere_config
remove_octoeverywhere_store_dir
print_confirm "OctoEverywhere was successfully removed!"
return
}
#===================================================#
#============= UPDATE ==============#
#===================================================#
function update_octoeverywhere() {
do_action_service "stop" "octoeverywhere"
if [[ ! -d ${OCTOEVERYWHERE_DIR} ]]; then
clone_octoeverywhere "${OCTOEVERYWHERE_REPO}"
else
backup_before_update "octoeverywhere"
status_msg "Updating OctoEverywhere for Klipper ..."
cd "${OCTOEVERYWHERE_DIR}" && git pull
### read PKGLIST and install possible new dependencies
install_octoeverywhere_dependencies
### install possible new python dependencies
"${OCTOEVERYWHERE_ENV}"/bin/pip install -r "${OCTOEVERYWHERE_DIR}/requirements.txt"
fi
ok_msg "Update complete!"
do_action_service "restart" "octoeverywhere"
}
function clone_octoeverywhere() {
local repo=${1}
status_msg "Cloning OctoEverywhere from ${repo} ..."
### force remove existing octoeverywhere dir and clone into fresh octoeverywhere dir
[[ -d ${OCTOEVERYWHERE_DIR} ]] && rm -rf "${OCTOEVERYWHERE_DIR}"
cd "${HOME}" || exit 1
if ! git clone "${OCTOEVERYWHERE_REPO}" "${OCTOEVERYWHERE_DIR}"; then
print_error "Cloning OctoEverywhere from\n ${repo}\n failed!"
exit 1
fi
}
function install_octoeverywhere_dependencies() {
local packages log_name="OctoEverywhere"
local install_script="${OCTOEVERYWHERE_DIR}/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[@]"
}
#===================================================#
#============= STATUS ==============#
#===================================================#
function get_octoeverywhere_status() {
local status
local service_count
local octoeverywhere_services
octoeverywhere_services=$(octoeverywhere_systemd)
service_count=$(echo "${octoeverywhere_services}" | wc -w )
if (( service_count == 0 )); then
status="Not installed!"
elif [[ ! -d "${OCTOEVERYWHERE_DIR}" ]]; then
status="Incomplete!"
else
status="Installed!"
fi
echo "${status}"
}
function get_local_octoeverywhere_commit() {
[[ ! -d ${OCTOEVERYWHERE_DIR} || ! -d "${OCTOEVERYWHERE_DIR}/.git" ]] && return
local commit
cd "${OCTOEVERYWHERE_DIR}"
commit="$(git describe HEAD --always --tags | cut -d "-" -f 1,2)"
echo "${commit}"
}
function get_remote_octoeverywhere_commit() {
[[ ! -d ${OCTOEVERYWHERE_DIR} || ! -d "${OCTOEVERYWHERE_DIR}/.git" ]] && return
local commit
cd "${OCTOEVERYWHERE_DIR}" && git fetch origin -q
commit=$(git describe origin/master --always --tags | cut -d "-" -f 1,2)
echo "${commit}"
}
function compare_octoeverywhere_versions() {
local versions local_ver remote_ver
local_ver="$(get_local_octoeverywhere_commit)"
remote_ver="$(get_remote_octoeverywhere_commit)"
if [[ ${local_ver} != "${remote_ver}" ]]; then
versions="${yellow}$(printf " %-14s" "${local_ver}")${white}"
versions+="|${green}$(printf " %-13s" "${remote_ver}")${white}"
# Add us to the update file, so if the user selects "update all" it includes us.
add_to_application_updates "octoeverywhere"
else
versions="${green}$(printf " %-14s" "${local_ver}")${white}"
versions+="|${green}$(printf " %-13s" "${remote_ver}")${white}"
fi
echo "${versions}"
}

View File

@@ -1,10 +1,10 @@
#!/usr/bin/env bash #!/usr/bin/env bash
#=======================================================================# #=======================================================================#
# Copyright (C) 2020 - 2022 Dominik Willner <th33xitus@gmail.com> # # Copyright (C) 2020 - 2023 Dominik Willner <th33xitus@gmail.com> #
# # # #
# This file is part of KIAUH - Klipper Installation And Update Helper # # This file is part of KIAUH - Klipper Installation And Update Helper #
# https://github.com/th33xitus/kiauh # # https://github.com/dw-0/kiauh #
# # # #
# This file may be distributed under the terms of the GNU GPLv3 license # # This file may be distributed under the terms of the GNU GPLv3 license #
#=======================================================================# #=======================================================================#
@@ -24,10 +24,16 @@ function octoprint_systemd() {
function octoprint_setup_dialog() { function octoprint_setup_dialog() {
status_msg "Initializing OctoPrint installation ..." status_msg "Initializing OctoPrint installation ..."
local klipper_services klipper_count user_input=() klipper_names=() local klipper_services
klipper_services=$(klipper_systemd) klipper_services=$(klipper_systemd)
klipper_count=$(echo "${klipper_services}" | wc -w ) if [[ -z ${klipper_services} ]]; then
local error="Klipper not installed! Please install Klipper first!"
log_error "OctoPrint setup started without Klipper being installed. Aborting setup."
print_error "${error}" && return
fi
local klipper_count user_input=() klipper_names=()
klipper_count=$(echo "${klipper_services}" | wc -w )
for service in ${klipper_services}; do for service in ${klipper_services}; do
klipper_names+=( "$(get_instance_name "${service}")" ) klipper_names+=( "$(get_instance_name "${service}")" )
done done
@@ -63,7 +69,7 @@ function octoprint_setup_dialog() {
done && select_msg "${octoprint_count}" done && select_msg "${octoprint_count}"
else else
log_error "Internal error. octoprint_count of '${octoprint_count}' not equal or grather than one!" log_error "Internal error. klipper_count of '${klipper_count}' not equal or grather than one!"
return 1 return 1
fi fi
@@ -221,21 +227,30 @@ function create_octoprint_service() {
local octoprint_count=${input[0]} && unset "input[0]" local octoprint_count=${input[0]} && unset "input[0]"
local names=("${input[@]}") && unset "input[@]" local names=("${input[@]}") && unset "input[@]"
local j=0 port=5000 local j=0 port=5000
local octo_env service basedir tmp_printer config_yaml restart_cmd local printer_data octo_env service basedir printer config_yaml restart_cmd
for (( i=1; i <= octoprint_count; i++ )); do for (( i=1; i <= octoprint_count; i++ )); do
if (( octoprint_count == 1 )); then if (( octoprint_count == 1 )); then
printer_data="${HOME}/printer_data"
octo_env="${HOME}/OctoPrint" octo_env="${HOME}/OctoPrint"
service="${SYSTEMD}/octoprint.service" service="${SYSTEMD}/octoprint.service"
basedir="${HOME}/.octoprint" basedir="${HOME}/.octoprint"
tmp_printer="/tmp/printer" printer="${printer_data}/comms/klippy.serial"
config_yaml="${basedir}/config.yaml" config_yaml="${basedir}/config.yaml"
restart_cmd="sudo service octoprint restart" restart_cmd="sudo service octoprint restart"
elif (( octoprint_count > 1 )); then elif (( octoprint_count > 1 )); then
local re="^[1-9][0-9]*$"
if [[ ${names[j]} =~ ${re} ]]; then
printer_data="${HOME}/printer_${names[${j}]}_data"
else
printer_data="${HOME}/${names[${j}]}_data"
fi
octo_env="${HOME}/OctoPrint_${names[${j}]}" octo_env="${HOME}/OctoPrint_${names[${j}]}"
service="${SYSTEMD}/octoprint-${names[${j}]}.service" service="${SYSTEMD}/octoprint-${names[${j}]}.service"
basedir="${HOME}/.octoprint_${names[${j}]}" basedir="${HOME}/.octoprint_${names[${j}]}"
tmp_printer="/tmp/printer-${names[${j}]}" printer="${printer_data}/comms/klippy.serial"
config_yaml="${basedir}/config.yaml" config_yaml="${basedir}/config.yaml"
restart_cmd="sudo service octoprint-${names[${j}]} restart" restart_cmd="sudo service octoprint-${names[${j}]} restart"
fi fi
@@ -274,9 +289,9 @@ OCTOPRINT
/bin/sh -c "cat > ${basedir}/config.yaml" << CONFIGYAML /bin/sh -c "cat > ${basedir}/config.yaml" << CONFIGYAML
serial: serial:
additionalPorts: additionalPorts:
- ${tmp_printer} - ${printer}
disconnectOnErrors: false disconnectOnErrors: false
port: ${tmp_printer} port: ${printer}
server: server:
commands: commands:
serverRestartCommand: ${restart_cmd} serverRestartCommand: ${restart_cmd}
@@ -398,4 +413,4 @@ function get_octoprint_status() {
fi fi
echo "${status}" echo "${status}"
} }

View File

@@ -1,10 +1,10 @@
#!/usr/bin/env bash #!/usr/bin/env bash
#=======================================================================# #=======================================================================#
# Copyright (C) 2020 - 2022 Dominik Willner <th33xitus@gmail.com> # # Copyright (C) 2020 - 2023 Dominik Willner <th33xitus@gmail.com> #
# # # #
# This file is part of KIAUH - Klipper Installation And Update Helper # # This file is part of KIAUH - Klipper Installation And Update Helper #
# https://github.com/th33xitus/kiauh # # https://github.com/dw-0/kiauh #
# # # #
# This file may be distributed under the terms of the GNU GPLv3 license # # This file may be distributed under the terms of the GNU GPLv3 license #
#=======================================================================# #=======================================================================#
@@ -38,7 +38,7 @@ function install_pgc_for_klipper() {
fi fi
sudo cp "${pgconfsrc}" "${pgconf}" sudo cp "${pgconfsrc}" "${pgconf}"
sudo sed -i "s|/home/pi/pgcode;|/home/${USER}/pgcode;|" "${pgconf}" sudo sed -i "s|/home/pi/pgcode;|${HOME}/pgcode;|" "${pgconf}"
### replace default port ### replace default port
if (( pgc_custom_port != pgc_default_port )); then if (( pgc_custom_port != pgc_default_port )); then
@@ -104,12 +104,11 @@ function get_remote_prettygcode_commit() {
[[ ! -d ${PGC_DIR} || ! -d "${PGC_DIR}/.git" ]] && return [[ ! -d ${PGC_DIR} || ! -d "${PGC_DIR}/.git" ]] && return
cd "${PGC_DIR}" && git fetch origin -q cd "${PGC_DIR}" && git fetch origin -q
commit=$(git describe origin/master --always --tags | cut -d "-" -f 1,2) commit=$(git describe origin/main --always --tags | cut -d "-" -f 1,2)
echo "${commit}" echo "${commit}"
} }
function compare_prettygcode_versions() { function compare_prettygcode_versions() {
unset PGC_UPDATE_AVAIL
local versions local_ver remote_ver local versions local_ver remote_ver
local_ver="$(get_local_prettygcode_commit)" local_ver="$(get_local_prettygcode_commit)"
remote_ver="$(get_remote_prettygcode_commit)" remote_ver="$(get_remote_prettygcode_commit)"
@@ -117,13 +116,12 @@ function compare_prettygcode_versions() {
if [[ ${local_ver} != "${remote_ver}" ]]; then if [[ ${local_ver} != "${remote_ver}" ]]; then
versions="${yellow}$(printf " %-14s" "${local_ver}")${white}" versions="${yellow}$(printf " %-14s" "${local_ver}")${white}"
versions+="|${green}$(printf " %-13s" "${remote_ver}")${white}" versions+="|${green}$(printf " %-13s" "${remote_ver}")${white}"
# add prettygcode to the update all array for the update all function in the updater # add moonraker to application_updates_available in kiauh.ini
PGC_UPDATE_AVAIL="true" && update_arr+=(update_pgc_for_klipper) add_to_application_updates "pgc_for_klipper"
else else
versions="${green}$(printf " %-14s" "${local_ver}")${white}" versions="${green}$(printf " %-14s" "${local_ver}")${white}"
versions+="|${green}$(printf " %-13s" "${remote_ver}")${white}" versions+="|${green}$(printf " %-13s" "${remote_ver}")${white}"
PGC_UPDATE_AVAIL="false"
fi fi
echo "${versions}" echo "${versions}"
} }

Some files were not shown because too many files have changed in this diff Show More