├── .editorconfig ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.yml │ ├── config.yml │ └── feature_request.yml └── workflows │ └── release-ff-and-tag.yml ├── .gitignore ├── .shellcheckrc ├── LICENSE ├── README.md ├── default.kiauh.cfg ├── docs ├── changelog.md └── gcode_shell_command.md ├── kiauh.py ├── kiauh.sh ├── kiauh ├── __init__.py ├── components │ ├── __init__.py │ ├── crowsnest │ │ ├── __init__.py │ │ └── crowsnest.py │ ├── klipper │ │ ├── __init__.py │ │ ├── assets │ │ │ ├── klipper.env │ │ │ ├── klipper.service │ │ │ └── printer.cfg │ │ ├── klipper.py │ │ ├── klipper_dialogs.py │ │ ├── klipper_utils.py │ │ ├── menus │ │ │ ├── __init__.py │ │ │ └── klipper_remove_menu.py │ │ └── services │ │ │ ├── __init__.py │ │ │ ├── klipper_instance_service.py │ │ │ └── klipper_setup_service.py │ ├── klipper_firmware │ │ ├── __init__.py │ │ ├── firmware_utils.py │ │ ├── flash_options.py │ │ └── menus │ │ │ ├── klipper_build_menu.py │ │ │ ├── klipper_flash_error_menu.py │ │ │ ├── klipper_flash_help_menu.py │ │ │ └── klipper_flash_menu.py │ ├── klipperscreen │ │ ├── __init__.py │ │ └── klipperscreen.py │ ├── log_uploads │ │ ├── __init__.py │ │ ├── log_upload_utils.py │ │ └── menus │ │ │ └── log_upload_menu.py │ ├── moonraker │ │ ├── __init__.py │ │ ├── assets │ │ │ ├── moonraker.conf │ │ │ ├── moonraker.env │ │ │ └── moonraker.service │ │ ├── menus │ │ │ ├── __init__.py │ │ │ └── moonraker_remove_menu.py │ │ ├── moonraker.py │ │ ├── moonraker_dialogs.py │ │ ├── services │ │ │ ├── __init__.py │ │ │ ├── moonraker_instance_service.py │ │ │ └── moonraker_setup_service.py │ │ └── utils │ │ │ ├── __init__.py │ │ │ ├── sysdeps_parser.py │ │ │ └── utils.py │ └── webui_client │ │ ├── __init__.py │ │ ├── assets │ │ ├── common_vars.conf │ │ ├── nginx_cfg │ │ └── upstreams.conf │ │ ├── base_data.py │ │ ├── client_config │ │ ├── __init__.py │ │ ├── client_config_remove.py │ │ └── client_config_setup.py │ │ ├── client_dialogs.py │ │ ├── client_remove.py │ │ ├── client_setup.py │ │ ├── client_utils.py │ │ ├── fluidd_data.py │ │ ├── mainsail_data.py │ │ └── menus │ │ ├── __init__.py │ │ ├── client_install_menu.py │ │ └── client_remove_menu.py ├── core │ ├── __init__.py │ ├── backup_manager │ │ ├── __init__.py │ │ └── backup_manager.py │ ├── constants.py │ ├── decorators.py │ ├── instance_manager │ │ ├── __init__.py │ │ ├── base_instance.py │ │ └── instance_manager.py │ ├── logger.py │ ├── menus │ │ ├── __init__.py │ │ ├── advanced_menu.py │ │ ├── backup_menu.py │ │ ├── base_menu.py │ │ ├── install_menu.py │ │ ├── main_menu.py │ │ ├── remove_menu.py │ │ ├── repo_select_menu.py │ │ ├── settings_menu.py │ │ └── update_menu.py │ ├── services │ │ ├── __init__.py │ │ └── message_service.py │ ├── settings │ │ ├── __init__.py │ │ └── kiauh_settings.py │ ├── spinner.py │ ├── submodules │ │ ├── __init__.py │ │ └── simple_config_parser │ │ │ ├── .editorconfig │ │ │ ├── .gitignore │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── pyproject.toml │ │ │ ├── requirements-dev.txt │ │ │ ├── src │ │ │ └── simple_config_parser │ │ │ │ ├── __init__.py │ │ │ │ ├── constants.py │ │ │ │ └── simple_config_parser.py │ │ │ └── tests │ │ │ ├── __init__.py │ │ │ ├── assets │ │ │ ├── klipper_config.txt │ │ │ ├── test_config_1.cfg │ │ │ ├── test_config_2.cfg │ │ │ ├── test_config_3.cfg │ │ │ └── write_tests │ │ │ │ ├── add_option │ │ │ │ ├── expected.cfg │ │ │ │ └── input.cfg │ │ │ │ ├── remove_option │ │ │ │ ├── expected.cfg │ │ │ │ └── input.cfg │ │ │ │ └── remove_section │ │ │ │ ├── expected.cfg │ │ │ │ └── input.cfg │ │ │ ├── line_matching │ │ │ ├── __init__.py │ │ │ ├── match_empty_line │ │ │ │ ├── __init__.py │ │ │ │ ├── test_data │ │ │ │ │ ├── matching_data.txt │ │ │ │ │ └── non_matching_data.txt │ │ │ │ └── test_match_empty_line.py │ │ │ ├── match_line_comment │ │ │ │ ├── __init__.py │ │ │ │ ├── test_data │ │ │ │ │ ├── matching_data.txt │ │ │ │ │ └── non_matching_data.txt │ │ │ │ └── test_match_line_comment.py │ │ │ ├── match_option │ │ │ │ ├── __init__.py │ │ │ │ ├── test_data │ │ │ │ │ ├── matching_data.txt │ │ │ │ │ └── non_matching_data.txt │ │ │ │ └── test_match_option.py │ │ │ ├── match_option_block_start │ │ │ │ ├── __init__.py │ │ │ │ ├── test_data │ │ │ │ │ ├── matching_data.txt │ │ │ │ │ └── non_matching_data.txt │ │ │ │ └── test_match_options_block_start.py │ │ │ └── match_section │ │ │ │ ├── __init__,py.py │ │ │ │ ├── test_data │ │ │ │ ├── matching_data.txt │ │ │ │ └── non_matching_data.txt │ │ │ │ └── test_match_section.py │ │ │ ├── line_parsing │ │ │ ├── __init__.py │ │ │ └── test_line_parsing.py │ │ │ ├── public_api │ │ │ ├── __init__.py │ │ │ ├── conftest.py │ │ │ ├── test_options_api.py │ │ │ ├── test_read_file.py │ │ │ ├── test_sections_api.py │ │ │ └── test_write_file.py │ │ │ ├── utils.py │ │ │ └── value_conversion │ │ │ ├── __init__.py │ │ │ └── test_get_conv.py │ └── types │ │ ├── __init__.py │ │ ├── color.py │ │ └── component_status.py ├── extensions │ ├── __init__.py │ ├── base_extension.py │ ├── extensions_menu.py │ ├── gcode_shell_cmd │ │ ├── __init__.py │ │ ├── assets │ │ │ ├── gcode_shell_command.py │ │ │ └── shell_command.cfg │ │ ├── gcode_shell_cmd_extension.py │ │ └── metadata.json │ ├── klipper_backup │ │ ├── __init__.py │ │ ├── klipper_backup_extension.py │ │ └── metadata.json │ ├── mainsail_theme_installer │ │ ├── __init__.py │ │ ├── mainsail_theme_installer_extension.py │ │ └── metadata.json │ ├── mobileraker │ │ ├── __init__.py │ │ ├── metadata.json │ │ └── mobileraker_extension.py │ ├── obico │ │ ├── __init__.py │ │ ├── assets │ │ │ ├── moonraker-obico.env │ │ │ └── moonraker-obico.service │ │ ├── metadata.json │ │ ├── moonraker_obico.py │ │ └── moonraker_obico_extension.py │ ├── octoapp │ │ ├── __init__.py │ │ ├── metadata.json │ │ ├── octoapp.py │ │ └── octoapp_extension.py │ ├── octoeverywhere │ │ ├── __init__.py │ │ ├── metadata.json │ │ ├── octoeverywhere.py │ │ └── octoeverywhere_extension.py │ ├── pretty_gcode │ │ ├── __init__.py │ │ ├── assets │ │ │ └── pgcode.local.conf │ │ ├── metadata.json │ │ └── pretty_gcode_extension.py │ ├── simply_print │ │ ├── __init__.py │ │ ├── metadata.json │ │ └── simply_print_extension.py │ ├── spoolman │ │ ├── __init__.py │ │ ├── assets │ │ │ └── docker-compose.yml │ │ ├── metadata.json │ │ ├── spoolman.py │ │ └── spoolman_extension.py │ └── telegram_bot │ │ ├── __init__.py │ │ ├── assets │ │ ├── moonraker-telegram-bot.env │ │ └── moonraker-telegram-bot.service │ │ ├── metadata.json │ │ ├── moonraker_telegram_bot.py │ │ └── moonraker_telegram_bot_extension.py ├── main.py ├── procedures │ ├── __init__.py │ ├── switch_repo.py │ └── system.py └── utils │ ├── __init__.py │ ├── common.py │ ├── config_utils.py │ ├── fs_utils.py │ ├── git_utils.py │ ├── input_utils.py │ ├── instance_type.py │ ├── instance_utils.py │ └── sys_utils.py ├── pyproject.toml ├── pyrightconfig.json ├── requirements-dev.txt ├── resources ├── autocommit.sh ├── common_vars.conf ├── example.printer.cfg ├── fluidd ├── gcode_shell_command.py ├── klipper.env ├── klipper.service ├── mainsail ├── mjpg-streamer │ ├── webcam.txt │ ├── webcamd │ └── webcamd.service ├── moonraker-telegram-bot.env ├── moonraker-telegram-bot.service ├── moonraker.conf ├── moonraker.env ├── moonraker.service ├── screenshots │ ├── kiauh.png │ ├── rpi_imager1.png │ └── rpi_imager2.png ├── shell_command.cfg └── upstreams.conf └── scripts ├── backup.sh ├── crowsnest.sh ├── flash_klipper.sh ├── fluidd.sh ├── gcode_shell_command.sh ├── globals.sh ├── klipper.sh ├── klipperscreen.sh ├── mainsail.sh ├── mjpg-streamer.sh ├── mobileraker.sh ├── moonraker-telegram-bot.sh ├── moonraker.sh ├── nginx.sh ├── obico.sh ├── octoapp.sh ├── octoeverywhere.sh ├── octoprint.sh ├── pretty_gcode.sh ├── rollback.sh ├── spoolman.sh ├── switch_klipper_repo.sh ├── ui ├── advanced_menu.sh ├── backup_menu.sh ├── general_ui.sh ├── install_menu.sh ├── main_menu.sh ├── remove_menu.sh ├── settings_menu.sh └── update_menu.sh ├── upload_log.sh └── utilities.sh /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 4 6 | insert_final_newline = true 7 | trim_trailing_whitespace = true 8 | charset = utf-8 9 | end_of_line = lf 10 | 11 | [*.py] 12 | max_line_length = 88 13 | 14 | [*.{sh,yml,yaml,json}] 15 | indent_size = 2 -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: dw__0 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: https://paypal.me/dwillner0 13 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: Bug report 2 | description: Create a report to help us improve 3 | labels: ["bug"] 4 | body: 5 | - type: markdown 6 | attributes: 7 | value: | 8 | This issue form is for reporting bugs only! 9 | If you have a feature request, please use [feature_request](/new?template=feature_request.yml) 10 | - type: textarea 11 | id: distro 12 | attributes: 13 | label: Linux Distribution 14 | description: >- 15 | The linux distribution the issue occured on 16 | validations: 17 | required: true 18 | - type: textarea 19 | id: what-happened 20 | attributes: 21 | label: What happened 22 | description: >- 23 | A clear and concise description of what the bug is. 24 | validations: 25 | required: true 26 | - type: textarea 27 | id: expected-behavior 28 | attributes: 29 | label: What did you expect to happen 30 | description: >- 31 | A clear and concise description of what you expected to happen. 32 | validations: 33 | required: true 34 | - type: textarea 35 | id: repro-steps 36 | attributes: 37 | label: How to reproduce 38 | description: >- 39 | Minimal and precise steps to reproduce this bug. 40 | validations: 41 | required: true 42 | - type: textarea 43 | id: additional-info 44 | attributes: 45 | label: Additional information 46 | description: | 47 | If you have any additional information for us, use the field below. 48 | 49 | Please note, you can attach screenshots or screen recordings here, by 50 | dragging and dropping files in the field below. 51 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Klipper Discord 4 | url: https://discord.klipper3d.org/ 5 | about: Quickest way to get in contact 6 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yml: -------------------------------------------------------------------------------- 1 | name: Feature request 2 | description: Suggest an idea for this project 3 | labels: ["feature request"] 4 | body: 5 | - type: markdown 6 | attributes: 7 | value: | 8 | This issue form is for feature requests only! 9 | If you've found a bug, please use [bug_report](/new?template=bug_report.yml) 10 | - type: textarea 11 | id: problem-description 12 | attributes: 13 | label: Is your feature request related to a problem? Please describe 14 | description: >- 15 | A clear and concise description of what the problem is. 16 | validations: 17 | required: true 18 | - type: textarea 19 | id: solution-description 20 | attributes: 21 | label: Describe the solution you'd like 22 | description: >- 23 | A clear and concise description of what you want to happen. 24 | validations: 25 | required: true 26 | - type: textarea 27 | id: possible-alternatives 28 | attributes: 29 | label: Describe alternatives you've considered 30 | description: >- 31 | A clear and concise description of any alternative solutions or features you've considered. 32 | - type: textarea 33 | id: additional-info 34 | attributes: 35 | label: Additional information 36 | description: | 37 | If you have any additional information for us, use the field below. 38 | 39 | Please note, you can attach screenshots or screen recordings here, by 40 | dragging and dropping files in the field below. 41 | -------------------------------------------------------------------------------- /.github/workflows/release-ff-and-tag.yml: -------------------------------------------------------------------------------- 1 | name: Release - Fast-Forward and Tag 2 | on: 3 | workflow_dispatch: 4 | inputs: 5 | tag_name: 6 | description: 'Provide a tag name (e.g. v1.0.0)' 7 | required: true 8 | type: string 9 | 10 | jobs: 11 | ff-and-tag: 12 | runs-on: ubuntu-latest 13 | permissions: 14 | contents: write 15 | steps: 16 | - name: Checkout 17 | uses: actions/checkout@v4 18 | with: 19 | fetch-depth: 0 20 | ref: 'master' 21 | - name: Merge Fast Forward 22 | uses: MaximeHeckel/github-action-merge-fast-forward@v1.1.0 23 | with: 24 | branchtomerge: origin/develop 25 | branch: master 26 | env: 27 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 28 | - name: Create and Push Tag 29 | run: | 30 | git tag ${{ inputs.tag_name }} 31 | git push origin ${{ inputs.tag_name }} 32 | env: 33 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .vscode 3 | .pytest_cache 4 | .jupyter 5 | *.ipynb 6 | *.ipynb_checkpoints 7 | *.tmp 8 | __pycache__ 9 | .kiauh-env 10 | *.code-workspace 11 | *.iml 12 | kiauh.cfg 13 | -------------------------------------------------------------------------------- /.shellcheckrc: -------------------------------------------------------------------------------- 1 | source=scripts 2 | 3 | enable=avoid-nullary-conditions 4 | enable=deprecate-which 5 | enable=quote-safe-variables 6 | enable=require-variable-braces 7 | enable=require-double-brackets 8 | 9 | # SC2162: `read` without `-r` will mangle backslashes. 10 | # https://github.com/koalaman/shellcheck/wiki/SC2162 11 | disable=SC2162 12 | 13 | # SC2164: Use `cd ... || exit` in case `cd` fails 14 | # https://github.com/koalaman/shellcheck/wiki/SC2164 15 | disable=SC2164 16 | -------------------------------------------------------------------------------- /default.kiauh.cfg: -------------------------------------------------------------------------------- 1 | [kiauh] 2 | backup_before_update: False 3 | 4 | [klipper] 5 | # add custom repositories here, if at least one is given, the first in the list will be used by default 6 | # otherwise the official repository is used 7 | # 8 | # format: https://github.com/username/repository, branch 9 | # example: https://github.com/Klipper3d/klipper, master 10 | # 11 | # branch is optional, if given, it must be preceded by a comma, if not given, 'master' is used 12 | repositories: 13 | https://github.com/Klipper3d/klipper 14 | 15 | [moonraker] 16 | # Moonraker supports two optional Python packages that can be used to reduce its CPU load 17 | # If set to true, those packages will be installed during the Moonraker installation 18 | optional_speedups: True 19 | 20 | # add custom repositories here, if at least one is given, the first in the list will be used by default 21 | # otherwise the official repository is used 22 | # 23 | # format: https://github.com/username/repository, branch 24 | # example: https://github.com/Arksine/moonraker, master 25 | # 26 | # branch is optional, if given, it must be preceded by a comma, if not given, 'master' is used 27 | repositories: 28 | https://github.com/Arksine/moonraker 29 | 30 | [mainsail] 31 | port: 80 32 | unstable_releases: False 33 | 34 | [fluidd] 35 | port: 80 36 | unstable_releases: False 37 | -------------------------------------------------------------------------------- /docs/gcode_shell_command.md: -------------------------------------------------------------------------------- 1 | # G-Code Shell Command Extension 2 | 3 | ### Creator of this extension is [Arksine](https://github.com/Arksine). 4 | 5 | This is a brief explanation of how to use the shell command extension for Klipper, which you can install with KIAUH. 6 | 7 | After installing the extension you can execute linux commands or even scripts from within Klipper with custom commands defined in your printer.cfg. 8 | 9 | #### How to configure a shell command: 10 | 11 | ```shell 12 | # Runs a linux command or script from within klipper. Note that sudo commands 13 | # that require password authentication are disallowed. All executable scripts 14 | # should include a shebang. 15 | # [gcode_shell_command my_shell_cmd] 16 | #command: 17 | # The linux shell command/script to be executed. This parameter must be 18 | # provided 19 | #timeout: 2. 20 | # The timeout in seconds until the command is forcably terminated. Default 21 | # is 2 seconds. 22 | #verbose: True 23 | # If enabled, the command's output will be forwarded to the terminal. Its 24 | # recommended to set this to false for commands that my run in quick 25 | # succession. Default is True. 26 | ``` 27 | 28 | Once you have set up a shell command with the given parameters from above in your printer.cfg you can run the command as follows: 29 | `RUN_SHELL_COMMAND CMD=name` 30 | 31 | Example: 32 | 33 | ``` 34 | [gcode_shell_command hello_world] 35 | command: echo hello world 36 | timeout: 2. 37 | verbose: True 38 | ``` 39 | 40 | Execute with: 41 | `RUN_SHELL_COMMAND CMD=hello_world` 42 | 43 | ### Passing parameters: 44 | 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`. 45 | 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, 46 | 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`) 47 | and writes the value of the parameter into a textfile called `test.txt` located in the home directory. 48 | 49 | Content of the `gcode_shell_command` and the `gcode_macro`: 50 | ``` 51 | [gcode_shell_command print_to_file] 52 | command: sh /home/pi/klipper_config/script.sh 53 | timeout: 30. 54 | verbose: True 55 | 56 | [gcode_macro GET_TEMP] 57 | gcode: 58 | {% set temp = printer.extruder.temperature %} 59 | { action_respond_info("%s" % (temp)) } 60 | RUN_SHELL_COMMAND CMD=print_to_file PARAMS={temp} 61 | ``` 62 | 63 | Content of `script.sh`: 64 | ```shell 65 | #!/bin/sh 66 | 67 | echo "temp is: $1" 68 | echo "$1" >> "${HOME}/test.txt" 69 | ``` 70 | 71 | ## Warning 72 | 73 | This extension may have a high potential for abuse if not used carefully! Also, depending on the command you execute, high system loads may occur and can cause system instabilities. 74 | Use this extension at your own risk and only if you know what you are doing! 75 | -------------------------------------------------------------------------------- /kiauh.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # ======================================================================= # 4 | # Copyright (C) 2020 - 2025 Dominik Willner <th33xitus@gmail.com> # 5 | # # 6 | # This file is part of KIAUH - Klipper Installation And Update Helper # 7 | # https://github.com/dw-0/kiauh # 8 | # # 9 | # This file may be distributed under the terms of the GNU GPLv3 license # 10 | # ======================================================================= # 11 | 12 | from kiauh.main import main 13 | 14 | if __name__ == "__main__": 15 | main() 16 | -------------------------------------------------------------------------------- /kiauh/__init__.py: -------------------------------------------------------------------------------- 1 | # ======================================================================= # 2 | # Copyright (C) 2020 - 2025 Dominik Willner <th33xitus@gmail.com> # 3 | # # 4 | # This file is part of KIAUH - Klipper Installation And Update Helper # 5 | # https://github.com/dw-0/kiauh # 6 | # # 7 | # This file may be distributed under the terms of the GNU GPLv3 license # 8 | # ======================================================================= # 9 | 10 | import sys 11 | from pathlib import Path 12 | 13 | PROJECT_ROOT = Path(__file__).resolve().parent.parent 14 | APPLICATION_ROOT = Path(__file__).resolve().parent 15 | sys.path.append(str(APPLICATION_ROOT)) 16 | -------------------------------------------------------------------------------- /kiauh/components/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dw-0/kiauh/33113e72e9a45ac1a4020d409b5932e60e4c77eb/kiauh/components/__init__.py -------------------------------------------------------------------------------- /kiauh/components/crowsnest/__init__.py: -------------------------------------------------------------------------------- 1 | # ======================================================================= # 2 | # Copyright (C) 2020 - 2025 Dominik Willner <th33xitus@gmail.com> # 3 | # # 4 | # This file is part of KIAUH - Klipper Installation And Update Helper # 5 | # https://github.com/dw-0/kiauh # 6 | # # 7 | # This file may be distributed under the terms of the GNU GPLv3 license # 8 | # ======================================================================= # 9 | 10 | from pathlib import Path 11 | 12 | from core.backup_manager import BACKUP_ROOT_DIR 13 | from core.constants import SYSTEMD 14 | 15 | # repo 16 | CROWSNEST_REPO = "https://github.com/mainsail-crew/crowsnest.git" 17 | 18 | # names 19 | CROWSNEST_SERVICE_NAME = "crowsnest.service" 20 | 21 | # directories 22 | CROWSNEST_DIR = Path.home().joinpath("crowsnest") 23 | CROWSNEST_BACKUP_DIR = BACKUP_ROOT_DIR.joinpath("crowsnest-backups") 24 | 25 | # files 26 | CROWSNEST_MULTI_CONFIG = CROWSNEST_DIR.joinpath("tools/.config") 27 | CROWSNEST_INSTALL_SCRIPT = CROWSNEST_DIR.joinpath("tools/install.sh") 28 | CROWSNEST_BIN_FILE = Path("/usr/local/bin/crowsnest") 29 | CROWSNEST_LOGROTATE_FILE = Path("/etc/logrotate.d/crowsnest") 30 | CROWSNEST_SERVICE_FILE = SYSTEMD.joinpath(CROWSNEST_SERVICE_NAME) 31 | -------------------------------------------------------------------------------- /kiauh/components/klipper/__init__.py: -------------------------------------------------------------------------------- 1 | # ======================================================================= # 2 | # Copyright (C) 2020 - 2025 Dominik Willner <th33xitus@gmail.com> # 3 | # # 4 | # This file is part of KIAUH - Klipper Installation And Update Helper # 5 | # https://github.com/dw-0/kiauh # 6 | # # 7 | # This file may be distributed under the terms of the GNU GPLv3 license # 8 | # ======================================================================= # 9 | 10 | from pathlib import Path 11 | 12 | from core.backup_manager import BACKUP_ROOT_DIR 13 | 14 | MODULE_PATH = Path(__file__).resolve().parent 15 | 16 | KLIPPER_REPO_URL = "https://github.com/Klipper3d/klipper.git" 17 | 18 | # names 19 | KLIPPER_LOG_NAME = "klippy.log" 20 | KLIPPER_CFG_NAME = "printer.cfg" 21 | KLIPPER_SERIAL_NAME = "klippy.serial" 22 | KLIPPER_UDS_NAME = "klippy.sock" 23 | KLIPPER_ENV_FILE_NAME = "klipper.env" 24 | KLIPPER_SERVICE_NAME = "klipper.service" 25 | 26 | # directories 27 | KLIPPER_DIR = Path.home().joinpath("klipper") 28 | KLIPPER_KCONFIGS_DIR = Path.home().joinpath("klipper-kconfigs") 29 | KLIPPER_ENV_DIR = Path.home().joinpath("klippy-env") 30 | KLIPPER_BACKUP_DIR = BACKUP_ROOT_DIR.joinpath("klipper-backups") 31 | 32 | # files 33 | KLIPPER_REQ_FILE = KLIPPER_DIR.joinpath("scripts/klippy-requirements.txt") 34 | KLIPPER_INSTALL_SCRIPT = KLIPPER_DIR.joinpath("scripts/install-ubuntu-22.04.sh") 35 | KLIPPER_SERVICE_TEMPLATE = MODULE_PATH.joinpath(f"assets/{KLIPPER_SERVICE_NAME}") 36 | KLIPPER_ENV_FILE_TEMPLATE = MODULE_PATH.joinpath(f"assets/{KLIPPER_ENV_FILE_NAME}") 37 | 38 | 39 | EXIT_KLIPPER_SETUP = "Exiting Klipper setup ..." 40 | -------------------------------------------------------------------------------- /kiauh/components/klipper/assets/klipper.env: -------------------------------------------------------------------------------- 1 | KLIPPER_ARGS="%KLIPPER_DIR%/klippy/klippy.py %CFG% -I %SERIAL% -l %LOG% -a %UDS%" 2 | -------------------------------------------------------------------------------- /kiauh/components/klipper/assets/klipper.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Klipper 3D Printer Firmware SV1 3 | Documentation=https://www.klipper3d.org/ 4 | After=network-online.target 5 | Wants=udev.target 6 | 7 | [Install] 8 | WantedBy=multi-user.target 9 | 10 | [Service] 11 | Type=simple 12 | User=%USER% 13 | RemainAfterExit=yes 14 | WorkingDirectory=%KLIPPER_DIR% 15 | EnvironmentFile=%ENV_FILE% 16 | ExecStart=%ENV%/bin/python $KLIPPER_ARGS 17 | Restart=always 18 | RestartSec=10 19 | -------------------------------------------------------------------------------- /kiauh/components/klipper/assets/printer.cfg: -------------------------------------------------------------------------------- 1 | [mcu] 2 | serial: /dev/serial/by-id/<your-mcu-id> 3 | 4 | [virtual_sdcard] 5 | path: %GCODES_DIR% 6 | on_error_gcode: CANCEL_PRINT 7 | 8 | [printer] 9 | kinematics: none 10 | max_velocity: 1000 11 | max_accel: 1000 12 | -------------------------------------------------------------------------------- /kiauh/components/klipper/menus/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dw-0/kiauh/33113e72e9a45ac1a4020d409b5932e60e4c77eb/kiauh/components/klipper/menus/__init__.py -------------------------------------------------------------------------------- /kiauh/components/klipper/services/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dw-0/kiauh/33113e72e9a45ac1a4020d409b5932e60e4c77eb/kiauh/components/klipper/services/__init__.py -------------------------------------------------------------------------------- /kiauh/components/klipper/services/klipper_instance_service.py: -------------------------------------------------------------------------------- 1 | # ======================================================================= # 2 | # Copyright (C) 2020 - 2025 Dominik Willner <th33xitus@gmail.com> # 3 | # # 4 | # This file is part of KIAUH - Klipper Installation And Update Helper # 5 | # https://github.com/dw-0/kiauh # 6 | # # 7 | # This file may be distributed under the terms of the GNU GPLv3 license # 8 | # ======================================================================= # 9 | from __future__ import annotations 10 | 11 | from typing import List 12 | 13 | from components.klipper.klipper import Klipper 14 | from utils.instance_utils import get_instances 15 | 16 | 17 | class KlipperInstanceService: 18 | __cls_instance = None 19 | __instances: List[Klipper] = [] 20 | 21 | def __new__(cls) -> "KlipperInstanceService": 22 | if cls.__cls_instance is None: 23 | cls.__cls_instance = super(KlipperInstanceService, cls).__new__(cls) 24 | return cls.__cls_instance 25 | 26 | def __init__(self) -> None: 27 | if not hasattr(self, "__initialized"): 28 | self.__initialized = False 29 | if self.__initialized: 30 | return 31 | self.__initialized = True 32 | 33 | def load_instances(self) -> None: 34 | self.__instances = get_instances(Klipper) 35 | 36 | def create_new_instance(self, suffix: str) -> Klipper: 37 | instance = Klipper(suffix) 38 | self.__instances.append(instance) 39 | return instance 40 | 41 | def get_all_instances(self) -> List[Klipper]: 42 | return self.__instances 43 | 44 | def get_instance_by_suffix(self, suffix: str) -> Klipper | None: 45 | instances: List[Klipper] = [i for i in self.__instances if i.suffix == suffix] 46 | return instances[0] if instances else None 47 | -------------------------------------------------------------------------------- /kiauh/components/klipper_firmware/__init__.py: -------------------------------------------------------------------------------- 1 | # ======================================================================= # 2 | # Copyright (C) 2020 - 2025 Dominik Willner <th33xitus@gmail.com> # 3 | # # 4 | # This file is part of KIAUH - Klipper Installation And Update Helper # 5 | # https://github.com/dw-0/kiauh # 6 | # # 7 | # This file may be distributed under the terms of the GNU GPLv3 license # 8 | # ======================================================================= # 9 | 10 | from components.klipper import KLIPPER_DIR 11 | 12 | SD_FLASH_SCRIPT = KLIPPER_DIR.joinpath("scripts/flash-sdcard.sh") 13 | -------------------------------------------------------------------------------- /kiauh/components/klipper_firmware/flash_options.py: -------------------------------------------------------------------------------- 1 | # ======================================================================= # 2 | # Copyright (C) 2020 - 2025 Dominik Willner <th33xitus@gmail.com> # 3 | # # 4 | # This file is part of KIAUH - Klipper Installation And Update Helper # 5 | # https://github.com/dw-0/kiauh # 6 | # # 7 | # This file may be distributed under the terms of the GNU GPLv3 license # 8 | # ======================================================================= # 9 | from __future__ import annotations 10 | 11 | from dataclasses import field 12 | from enum import Enum 13 | from typing import List 14 | 15 | 16 | class FlashMethod(Enum): 17 | REGULAR = "Regular" 18 | SD_CARD = "SD Card" 19 | 20 | 21 | class FlashCommand(Enum): 22 | FLASH = "flash" 23 | SERIAL_FLASH = "serialflash" 24 | 25 | 26 | class ConnectionType(Enum): 27 | USB = "USB" 28 | USB_DFU = "USB (DFU)" 29 | USB_RP2040 = "USB (RP2040)" 30 | UART = "UART" 31 | 32 | 33 | class FlashOptions: 34 | _instance = None 35 | _flash_method: FlashMethod | None = None 36 | _flash_command: FlashCommand | None = None 37 | _connection_type: ConnectionType | None = None 38 | _mcu_list: List[str] = field(default_factory=list) 39 | _selected_mcu: str = "" 40 | _selected_board: str = "" 41 | _selected_baudrate: int = 250000 42 | _selected_kconfig: str = ".config" 43 | 44 | def __new__(cls, *args, **kwargs): 45 | if not cls._instance: 46 | cls._instance = super(FlashOptions, cls).__new__(cls, *args, **kwargs) 47 | return cls._instance 48 | 49 | @classmethod 50 | def destroy(cls) -> None: 51 | cls._instance = None 52 | 53 | @property 54 | def flash_method(self) -> FlashMethod | None: 55 | return self._flash_method 56 | 57 | @flash_method.setter 58 | def flash_method(self, value: FlashMethod | None): 59 | self._flash_method = value 60 | 61 | @property 62 | def flash_command(self) -> FlashCommand | None: 63 | return self._flash_command 64 | 65 | @flash_command.setter 66 | def flash_command(self, value: FlashCommand | None): 67 | self._flash_command = value 68 | 69 | @property 70 | def connection_type(self) -> ConnectionType | None: 71 | return self._connection_type 72 | 73 | @connection_type.setter 74 | def connection_type(self, value: ConnectionType | None): 75 | self._connection_type = value 76 | 77 | @property 78 | def mcu_list(self) -> List[str]: 79 | return self._mcu_list 80 | 81 | @mcu_list.setter 82 | def mcu_list(self, value: List[str]) -> None: 83 | self._mcu_list = value 84 | 85 | @property 86 | def selected_mcu(self) -> str: 87 | return self._selected_mcu 88 | 89 | @selected_mcu.setter 90 | def selected_mcu(self, value: str) -> None: 91 | self._selected_mcu = value 92 | 93 | @property 94 | def selected_board(self) -> str: 95 | return self._selected_board 96 | 97 | @selected_board.setter 98 | def selected_board(self, value: str) -> None: 99 | self._selected_board = value 100 | 101 | @property 102 | def selected_baudrate(self) -> int: 103 | return self._selected_baudrate 104 | 105 | @selected_baudrate.setter 106 | def selected_baudrate(self, value: int) -> None: 107 | self._selected_baudrate = value 108 | 109 | @property 110 | def selected_kconfig(self) -> str: 111 | return self._selected_kconfig 112 | 113 | @selected_kconfig.setter 114 | def selected_kconfig(self, value: str) -> None: 115 | self._selected_kconfig = value 116 | -------------------------------------------------------------------------------- /kiauh/components/klipperscreen/__init__.py: -------------------------------------------------------------------------------- 1 | # ======================================================================= # 2 | # Copyright (C) 2020 - 2025 Dominik Willner <th33xitus@gmail.com> # 3 | # # 4 | # This file is part of KIAUH - Klipper Installation And Update Helper # 5 | # https://github.com/dw-0/kiauh # 6 | # # 7 | # This file may be distributed under the terms of the GNU GPLv3 license # 8 | # ======================================================================= # 9 | from pathlib import Path 10 | 11 | from core.backup_manager import BACKUP_ROOT_DIR 12 | from core.constants import SYSTEMD 13 | 14 | # repo 15 | KLIPPERSCREEN_REPO = "https://github.com/KlipperScreen/KlipperScreen.git" 16 | 17 | # names 18 | KLIPPERSCREEN_SERVICE_NAME = "KlipperScreen.service" 19 | KLIPPERSCREEN_UPDATER_SECTION_NAME = "update_manager KlipperScreen" 20 | KLIPPERSCREEN_LOG_NAME = "KlipperScreen.log" 21 | 22 | # directories 23 | KLIPPERSCREEN_DIR = Path.home().joinpath("KlipperScreen") 24 | KLIPPERSCREEN_ENV_DIR = Path.home().joinpath(".KlipperScreen-env") 25 | KLIPPERSCREEN_BACKUP_DIR = BACKUP_ROOT_DIR.joinpath("klipperscreen-backups") 26 | 27 | # files 28 | KLIPPERSCREEN_REQ_FILE = KLIPPERSCREEN_DIR.joinpath( 29 | "scripts/KlipperScreen-requirements.txt" 30 | ) 31 | KLIPPERSCREEN_INSTALL_SCRIPT = KLIPPERSCREEN_DIR.joinpath( 32 | "scripts/KlipperScreen-install.sh" 33 | ) 34 | KLIPPERSCREEN_SERVICE_FILE = SYSTEMD.joinpath(KLIPPERSCREEN_SERVICE_NAME) 35 | -------------------------------------------------------------------------------- /kiauh/components/log_uploads/__init__.py: -------------------------------------------------------------------------------- 1 | # ======================================================================= # 2 | # Copyright (C) 2020 - 2025 Dominik Willner <th33xitus@gmail.com> # 3 | # # 4 | # This file is part of KIAUH - Klipper Installation And Update Helper # 5 | # https://github.com/dw-0/kiauh # 6 | # # 7 | # This file may be distributed under the terms of the GNU GPLv3 license # 8 | # ======================================================================= # 9 | 10 | from pathlib import Path 11 | from typing import Dict, Literal, Union 12 | 13 | FileKey = Literal["filepath", "display_name"] 14 | LogFile = Dict[FileKey, Union[str, Path]] 15 | -------------------------------------------------------------------------------- /kiauh/components/log_uploads/log_upload_utils.py: -------------------------------------------------------------------------------- 1 | # ======================================================================= # 2 | # Copyright (C) 2020 - 2025 Dominik Willner <th33xitus@gmail.com> # 3 | # # 4 | # This file is part of KIAUH - Klipper Installation And Update Helper # 5 | # https://github.com/dw-0/kiauh # 6 | # # 7 | # This file may be distributed under the terms of the GNU GPLv3 license # 8 | # ======================================================================= # 9 | 10 | import urllib.request 11 | from pathlib import Path 12 | from typing import List 13 | 14 | from components.klipper.klipper import Klipper 15 | from components.log_uploads import LogFile 16 | from core.logger import Logger 17 | from utils.instance_utils import get_instances 18 | 19 | 20 | def get_logfile_list() -> List[LogFile]: 21 | log_dirs: List[Path] = [ 22 | instance.base.log_dir for instance in get_instances(Klipper) 23 | ] 24 | 25 | logfiles: List[LogFile] = [] 26 | for _dir in log_dirs: 27 | for f in _dir.iterdir(): 28 | logfiles.append({"filepath": f, "display_name": get_display_name(f)}) 29 | 30 | return logfiles 31 | 32 | 33 | def get_display_name(filepath: Path) -> str: 34 | printer = " ".join(filepath.parts[-3].split("_")[:-1]) 35 | name = filepath.name 36 | 37 | return f"{printer}: {name}" 38 | 39 | 40 | def upload_logfile(logfile: LogFile) -> None: 41 | file = logfile.get("filepath") 42 | name = logfile.get("display_name") 43 | Logger.print_status(f"Uploading the following logfile from {name} ...") 44 | 45 | with open(file, "rb") as f: 46 | headers = {"x-random": ""} 47 | req = urllib.request.Request("http://paste.c-net.org/", headers=headers, data=f) 48 | try: 49 | response = urllib.request.urlopen(req) 50 | link = response.read().decode("utf-8") 51 | Logger.print_ok("Upload successful! Access it via the following link:") 52 | Logger.print_ok(f">>>> {link}", False) 53 | except Exception as e: 54 | Logger.print_error("Uploading logfile failed!") 55 | Logger.print_error(str(e)) 56 | -------------------------------------------------------------------------------- /kiauh/components/log_uploads/menus/log_upload_menu.py: -------------------------------------------------------------------------------- 1 | # ======================================================================= # 2 | # Copyright (C) 2020 - 2025 Dominik Willner <th33xitus@gmail.com> # 3 | # # 4 | # This file is part of KIAUH - Klipper Installation And Update Helper # 5 | # https://github.com/dw-0/kiauh # 6 | # # 7 | # This file may be distributed under the terms of the GNU GPLv3 license # 8 | # ======================================================================= # 9 | from __future__ import annotations 10 | 11 | import textwrap 12 | from typing import Type 13 | 14 | from components.log_uploads.log_upload_utils import get_logfile_list, upload_logfile 15 | from core.logger import Logger 16 | from core.menus import Option 17 | from core.menus.base_menu import BaseMenu 18 | from core.types.color import Color 19 | 20 | 21 | # noinspection PyMethodMayBeStatic 22 | class LogUploadMenu(BaseMenu): 23 | def __init__(self, previous_menu: Type[BaseMenu] | None = None): 24 | super().__init__() 25 | self.title = "Log Upload" 26 | self.title_color = Color.YELLOW 27 | self.previous_menu: Type[BaseMenu] | None = previous_menu 28 | self.logfile_list = get_logfile_list() 29 | 30 | def set_previous_menu(self, previous_menu: Type[BaseMenu] | None) -> None: 31 | from core.menus.main_menu import MainMenu 32 | 33 | self.previous_menu = previous_menu if previous_menu is not None else MainMenu 34 | 35 | def set_options(self) -> None: 36 | self.options = { 37 | f"{index}": Option(self.upload, opt_index=f"{index}") 38 | for index in range(len(self.logfile_list)) 39 | } 40 | 41 | def print_menu(self) -> None: 42 | menu = textwrap.dedent( 43 | """ 44 | ╟───────────────────────────────────────────────────────╢ 45 | ║ You can select the following logfiles for uploading: ║ 46 | ║ ║ 47 | """ 48 | )[1:] 49 | 50 | for logfile in enumerate(self.logfile_list): 51 | line = f"{logfile[0]}) {logfile[1].get('display_name')}" 52 | menu += f"║ {line:<54}║\n" 53 | menu += "╟───────────────────────────────────────────────────────╢\n" 54 | 55 | print(menu, end="") 56 | 57 | def upload(self, **kwargs): 58 | try: 59 | index: int | None = kwargs.get("opt_index", None) 60 | if index is None: 61 | raise Exception("opt_index is None") 62 | 63 | index = int(index) 64 | upload_logfile(self.logfile_list[index]) 65 | except Exception as e: 66 | Logger.print_error(e) 67 | Logger.print_error("Log upload failed!") 68 | -------------------------------------------------------------------------------- /kiauh/components/moonraker/__init__.py: -------------------------------------------------------------------------------- 1 | # ======================================================================= # 2 | # Copyright (C) 2020 - 2025 Dominik Willner <th33xitus@gmail.com> # 3 | # # 4 | # This file is part of KIAUH - Klipper Installation And Update Helper # 5 | # https://github.com/dw-0/kiauh # 6 | # # 7 | # This file may be distributed under the terms of the GNU GPLv3 license # 8 | # ======================================================================= # 9 | 10 | from pathlib import Path 11 | 12 | from core.backup_manager import BACKUP_ROOT_DIR 13 | 14 | MODULE_PATH = Path(__file__).resolve().parent 15 | 16 | MOONRAKER_REPO_URL = "https://github.com/Arksine/moonraker.git" 17 | 18 | # names 19 | MOONRAKER_CFG_NAME = "moonraker.conf" 20 | MOONRAKER_LOG_NAME = "moonraker.log" 21 | MOONRAKER_SERVICE_NAME = "moonraker.service" 22 | MOONRAKER_DEFAULT_PORT = 7125 23 | MOONRAKER_ENV_FILE_NAME = "moonraker.env" 24 | 25 | # directories 26 | MOONRAKER_DIR = Path.home().joinpath("moonraker") 27 | MOONRAKER_ENV_DIR = Path.home().joinpath("moonraker-env") 28 | MOONRAKER_BACKUP_DIR = BACKUP_ROOT_DIR.joinpath("moonraker-backups") 29 | MOONRAKER_DB_BACKUP_DIR = BACKUP_ROOT_DIR.joinpath("moonraker-db-backups") 30 | 31 | # files 32 | MOONRAKER_INSTALL_SCRIPT = MOONRAKER_DIR.joinpath("scripts/install-moonraker.sh") 33 | MOONRAKER_REQ_FILE = MOONRAKER_DIR.joinpath("scripts/moonraker-requirements.txt") 34 | MOONRAKER_SPEEDUPS_REQ_FILE = MOONRAKER_DIR.joinpath("scripts/moonraker-speedups.txt") 35 | MOONRAKER_DEPS_JSON_FILE = MOONRAKER_DIR.joinpath("scripts/system-dependencies.json") 36 | # introduced due to 37 | # https://github.com/Arksine/moonraker/issues/349 38 | # https://github.com/Arksine/moonraker/pull/346 39 | POLKIT_LEGACY_FILE = Path("/etc/polkit-1/localauthority/50-local.d/10-moonraker.pkla") 40 | POLKIT_FILE = Path("/etc/polkit-1/rules.d/moonraker.rules") 41 | POLKIT_USR_FILE = Path("/usr/share/polkit-1/rules.d/moonraker.rules") 42 | POLKIT_SCRIPT = MOONRAKER_DIR.joinpath("scripts/set-policykit-rules.sh") 43 | MOONRAKER_SERVICE_TEMPLATE = MODULE_PATH.joinpath(f"assets/{MOONRAKER_SERVICE_NAME}") 44 | MOONRAKER_ENV_FILE_TEMPLATE = MODULE_PATH.joinpath(f"assets/{MOONRAKER_ENV_FILE_NAME}") 45 | 46 | 47 | EXIT_MOONRAKER_SETUP = "Exiting Moonraker setup ..." 48 | -------------------------------------------------------------------------------- /kiauh/components/moonraker/assets/moonraker.conf: -------------------------------------------------------------------------------- 1 | [server] 2 | host: 0.0.0.0 3 | port: %PORT% 4 | klippy_uds_address: %UDS% 5 | 6 | [authorization] 7 | trusted_clients: 8 | 10.0.0.0/8 9 | 127.0.0.0/8 10 | 169.254.0.0/16 11 | 172.16.0.0/12 12 | 192.168.0.0/16 13 | FC00::/7 14 | FE80::/10 15 | ::1/128 16 | cors_domains: 17 | *.lan 18 | *.local 19 | *://localhost 20 | *://localhost:* 21 | *://my.mainsail.xyz 22 | *://app.fluidd.xyz 23 | 24 | [octoprint_compat] 25 | 26 | [history] 27 | 28 | [update_manager] 29 | channel: dev 30 | refresh_interval: 168 31 | -------------------------------------------------------------------------------- /kiauh/components/moonraker/assets/moonraker.env: -------------------------------------------------------------------------------- 1 | MOONRAKER_ARGS="%MOONRAKER_DIR%/moonraker/moonraker.py -d %PRINTER_DATA%" -------------------------------------------------------------------------------- /kiauh/components/moonraker/assets/moonraker.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=API Server for Klipper SV1 3 | Documentation=https://moonraker.readthedocs.io/ 4 | Requires=network-online.target 5 | After=network-online.target 6 | 7 | [Install] 8 | WantedBy=multi-user.target 9 | 10 | [Service] 11 | Type=simple 12 | User=%USER% 13 | SupplementaryGroups=moonraker-admin 14 | RemainAfterExit=yes 15 | WorkingDirectory=%MOONRAKER_DIR% 16 | EnvironmentFile=%ENV_FILE% 17 | ExecStart=%ENV%/bin/python $MOONRAKER_ARGS 18 | Restart=always 19 | RestartSec=10 20 | -------------------------------------------------------------------------------- /kiauh/components/moonraker/menus/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dw-0/kiauh/33113e72e9a45ac1a4020d409b5932e60e4c77eb/kiauh/components/moonraker/menus/__init__.py -------------------------------------------------------------------------------- /kiauh/components/moonraker/moonraker_dialogs.py: -------------------------------------------------------------------------------- 1 | # ======================================================================= # 2 | # Copyright (C) 2020 - 2025 Dominik Willner <th33xitus@gmail.com> # 3 | # # 4 | # This file is part of KIAUH - Klipper Installation And Update Helper # 5 | # https://github.com/dw-0/kiauh # 6 | # # 7 | # This file may be distributed under the terms of the GNU GPLv3 license # 8 | # ======================================================================= # 9 | 10 | import textwrap 11 | from typing import List 12 | 13 | from components.klipper.klipper import Klipper 14 | from components.moonraker.moonraker import Moonraker 15 | from core.menus.base_menu import print_back_footer 16 | from core.types.color import Color 17 | 18 | 19 | def print_moonraker_overview( 20 | klipper_instances: List[Klipper], 21 | moonraker_instances: List[Moonraker], 22 | show_index=False, 23 | show_select_all=False, 24 | ): 25 | headline = Color.apply("The following instances were found:", Color.GREEN) 26 | dialog = textwrap.dedent( 27 | f""" 28 | ╔═══════════════════════════════════════════════════════╗ 29 | ║{headline:^64}║ 30 | ╟───────────────────────────────────────────────────────╢ 31 | """ 32 | )[1:] 33 | 34 | if show_select_all: 35 | select_all = Color.apply("a) Select all", Color.YELLOW) 36 | dialog += f"║ {select_all:<63}║\n" 37 | dialog += "║ ║\n" 38 | 39 | instance_map = { 40 | k.service_file_path.stem: ( 41 | k.service_file_path.stem.replace("klipper", "moonraker") 42 | if k.suffix in [m.suffix for m in moonraker_instances] 43 | else "" 44 | ) 45 | for k in klipper_instances 46 | } 47 | 48 | for i, k in enumerate(instance_map): 49 | mr_name = instance_map.get(k) 50 | m = f"<-> {mr_name}" if mr_name != "" else "" 51 | line = Color.apply(f"{f'{i + 1})' if show_index else '●'} {k} {m}", Color.CYAN) 52 | dialog += f"║ {line:<63}║\n" 53 | 54 | warn_l1 = Color.apply("PLEASE NOTE:", Color.YELLOW) 55 | warn_l2 = Color.apply( 56 | "If you select an instance with an existing Moonraker", Color.YELLOW 57 | ) 58 | warn_l3 = Color.apply( 59 | "instance, that Moonraker instance will be re-created!", Color.YELLOW 60 | ) 61 | warning = textwrap.dedent( 62 | f""" 63 | ║ ║ 64 | ╟───────────────────────────────────────────────────────╢ 65 | ║ {warn_l1:<63}║ 66 | ║ {warn_l2:<63}║ 67 | ║ {warn_l3:<63}║ 68 | ╟───────────────────────────────────────────────────────╢ 69 | """ 70 | )[1:] 71 | 72 | dialog += warning 73 | 74 | print(dialog, end="") 75 | print_back_footer() 76 | -------------------------------------------------------------------------------- /kiauh/components/moonraker/services/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dw-0/kiauh/33113e72e9a45ac1a4020d409b5932e60e4c77eb/kiauh/components/moonraker/services/__init__.py -------------------------------------------------------------------------------- /kiauh/components/moonraker/services/moonraker_instance_service.py: -------------------------------------------------------------------------------- 1 | # ======================================================================= # 2 | # Copyright (C) 2020 - 2025 Dominik Willner <th33xitus@gmail.com> # 3 | # # 4 | # This file is part of KIAUH - Klipper Installation And Update Helper # 5 | # https://github.com/dw-0/kiauh # 6 | # # 7 | # This file may be distributed under the terms of the GNU GPLv3 license # 8 | # ======================================================================= # 9 | from __future__ import annotations 10 | 11 | from typing import Dict, List 12 | 13 | from components.moonraker.moonraker import Moonraker 14 | from utils.instance_utils import get_instances 15 | 16 | 17 | class MoonrakerInstanceService: 18 | __cls_instance = None 19 | __instances: List[Moonraker] = [] 20 | 21 | def __new__(cls) -> "MoonrakerInstanceService": 22 | if cls.__cls_instance is None: 23 | cls.__cls_instance = super(MoonrakerInstanceService, cls).__new__(cls) 24 | return cls.__cls_instance 25 | 26 | def __init__(self) -> None: 27 | if not hasattr(self, "__initialized"): 28 | self.__initialized = False 29 | if self.__initialized: 30 | return 31 | self.__initialized = True 32 | 33 | def load_instances(self) -> None: 34 | self.__instances = get_instances(Moonraker) 35 | 36 | def create_new_instance(self, suffix: str) -> Moonraker: 37 | instance = Moonraker(suffix) 38 | self.__instances.append(instance) 39 | return instance 40 | 41 | def get_all_instances(self) -> List[Moonraker]: 42 | return self.__instances 43 | 44 | def get_instance_by_suffix(self, suffix: str) -> Moonraker | None: 45 | instances: List[Moonraker] = [i for i in self.__instances if i.suffix == suffix] 46 | return instances[0] if instances else None 47 | 48 | def get_instance_port_map(self) -> Dict[str, int]: 49 | return {i.suffix: i.port for i in self.__instances} 50 | -------------------------------------------------------------------------------- /kiauh/components/moonraker/utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dw-0/kiauh/33113e72e9a45ac1a4020d409b5932e60e4c77eb/kiauh/components/moonraker/utils/__init__.py -------------------------------------------------------------------------------- /kiauh/components/webui_client/__init__.py: -------------------------------------------------------------------------------- 1 | # ======================================================================= # 2 | # Copyright (C) 2020 - 2025 Dominik Willner <th33xitus@gmail.com> # 3 | # # 4 | # This file is part of KIAUH - Klipper Installation And Update Helper # 5 | # https://github.com/dw-0/kiauh # 6 | # # 7 | # This file may be distributed under the terms of the GNU GPLv3 license # 8 | # ======================================================================= # 9 | 10 | from pathlib import Path 11 | 12 | MODULE_PATH = Path(__file__).resolve().parent 13 | -------------------------------------------------------------------------------- /kiauh/components/webui_client/assets/common_vars.conf: -------------------------------------------------------------------------------- 1 | # /etc/nginx/conf.d/common_vars.conf 2 | 3 | map $http_upgrade $connection_upgrade { 4 | default upgrade; 5 | '' close; 6 | } -------------------------------------------------------------------------------- /kiauh/components/webui_client/assets/nginx_cfg: -------------------------------------------------------------------------------- 1 | server { 2 | listen %PORT%; 3 | # uncomment the next line to activate IPv6 4 | # listen [::]:%PORT%; 5 | 6 | access_log /var/log/nginx/%NAME%-access.log; 7 | error_log /var/log/nginx/%NAME%-error.log; 8 | 9 | # disable this section on smaller hardware like a pi zero 10 | gzip on; 11 | gzip_vary on; 12 | gzip_proxied any; 13 | gzip_proxied expired no-cache no-store private auth; 14 | gzip_comp_level 4; 15 | gzip_buffers 16 8k; 16 | gzip_http_version 1.1; 17 | gzip_types text/plain text/css text/xml text/javascript application/javascript application/x-javascript application/json application/xml; 18 | 19 | # web_path from %NAME% static files 20 | root %ROOT_DIR%; 21 | 22 | index index.html; 23 | server_name _; 24 | 25 | # disable max upload size checks 26 | client_max_body_size 0; 27 | 28 | # disable proxy request buffering 29 | proxy_request_buffering off; 30 | 31 | location / { 32 | try_files $uri $uri/ /index.html; 33 | } 34 | 35 | location = /index.html { 36 | add_header Cache-Control "no-store, no-cache, must-revalidate"; 37 | } 38 | 39 | location /websocket { 40 | proxy_pass http://apiserver/websocket; 41 | proxy_http_version 1.1; 42 | proxy_set_header Upgrade $http_upgrade; 43 | proxy_set_header Connection $connection_upgrade; 44 | proxy_set_header Host $http_host; 45 | proxy_set_header X-Real-IP $remote_addr; 46 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 47 | proxy_read_timeout 86400; 48 | } 49 | 50 | location ~ ^/(printer|api|access|machine|server)/ { 51 | proxy_pass http://apiserver$request_uri; 52 | proxy_http_version 1.1; 53 | proxy_set_header Upgrade $http_upgrade; 54 | proxy_set_header Host $http_host; 55 | proxy_set_header X-Real-IP $remote_addr; 56 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 57 | proxy_set_header X-Scheme $scheme; 58 | } 59 | 60 | location /webcam/ { 61 | postpone_output 0; 62 | proxy_buffering off; 63 | proxy_ignore_headers X-Accel-Buffering; 64 | access_log off; 65 | error_log off; 66 | proxy_pass http://mjpgstreamer1/; 67 | } 68 | 69 | location /webcam2/ { 70 | postpone_output 0; 71 | proxy_buffering off; 72 | proxy_ignore_headers X-Accel-Buffering; 73 | access_log off; 74 | error_log off; 75 | proxy_pass http://mjpgstreamer2/; 76 | } 77 | 78 | location /webcam3/ { 79 | postpone_output 0; 80 | proxy_buffering off; 81 | proxy_ignore_headers X-Accel-Buffering; 82 | access_log off; 83 | error_log off; 84 | proxy_pass http://mjpgstreamer3/; 85 | } 86 | 87 | location /webcam4/ { 88 | postpone_output 0; 89 | proxy_buffering off; 90 | proxy_ignore_headers X-Accel-Buffering; 91 | access_log off; 92 | error_log off; 93 | proxy_pass http://mjpgstreamer4/; 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /kiauh/components/webui_client/assets/upstreams.conf: -------------------------------------------------------------------------------- 1 | # /etc/nginx/conf.d/upstreams.conf 2 | upstream apiserver { 3 | ip_hash; 4 | server 127.0.0.1:7125; 5 | } 6 | 7 | upstream mjpgstreamer1 { 8 | ip_hash; 9 | server 127.0.0.1:8080; 10 | } 11 | 12 | upstream mjpgstreamer2 { 13 | ip_hash; 14 | server 127.0.0.1:8081; 15 | } 16 | 17 | upstream mjpgstreamer3 { 18 | ip_hash; 19 | server 127.0.0.1:8082; 20 | } 21 | 22 | upstream mjpgstreamer4 { 23 | ip_hash; 24 | server 127.0.0.1:8083; 25 | } -------------------------------------------------------------------------------- /kiauh/components/webui_client/base_data.py: -------------------------------------------------------------------------------- 1 | # ======================================================================= # 2 | # Copyright (C) 2020 - 2025 Dominik Willner <th33xitus@gmail.com> # 3 | # # 4 | # This file is part of KIAUH - Klipper Installation And Update Helper # 5 | # https://github.com/dw-0/kiauh # 6 | # # 7 | # This file may be distributed under the terms of the GNU GPLv3 license # 8 | # ======================================================================= # 9 | 10 | from __future__ import annotations 11 | 12 | from abc import ABC 13 | from dataclasses import dataclass 14 | from enum import Enum 15 | from pathlib import Path 16 | 17 | 18 | class WebClientType(Enum): 19 | MAINSAIL: str = "mainsail" 20 | FLUIDD: str = "fluidd" 21 | 22 | 23 | class WebClientConfigType(Enum): 24 | MAINSAIL: str = "mainsail-config" 25 | FLUIDD: str = "fluidd-config" 26 | 27 | 28 | @dataclass() 29 | class BaseWebClient(ABC): 30 | """Base class for webclient data""" 31 | 32 | client: WebClientType 33 | name: str 34 | display_name: str 35 | client_dir: Path 36 | config_file: Path 37 | backup_dir: Path 38 | repo_path: str 39 | download_url: str 40 | nginx_config: Path 41 | nginx_access_log: Path 42 | nginx_error_log: Path 43 | client_config: BaseWebClientConfig 44 | 45 | 46 | @dataclass() 47 | class BaseWebClientConfig(ABC): 48 | """Base class for webclient config data""" 49 | 50 | client_config: WebClientConfigType 51 | name: str 52 | display_name: str 53 | config_filename: str 54 | config_dir: Path 55 | backup_dir: Path 56 | repo_url: str 57 | config_section: str 58 | -------------------------------------------------------------------------------- /kiauh/components/webui_client/client_config/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dw-0/kiauh/33113e72e9a45ac1a4020d409b5932e60e4c77eb/kiauh/components/webui_client/client_config/__init__.py -------------------------------------------------------------------------------- /kiauh/components/webui_client/client_config/client_config_remove.py: -------------------------------------------------------------------------------- 1 | # ======================================================================= # 2 | # Copyright (C) 2020 - 2025 Dominik Willner <th33xitus@gmail.com> # 3 | # # 4 | # This file is part of KIAUH - Klipper Installation And Update Helper # 5 | # https://github.com/dw-0/kiauh # 6 | # # 7 | # This file may be distributed under the terms of the GNU GPLv3 license # 8 | # ======================================================================= # 9 | 10 | 11 | from typing import List 12 | 13 | from components.klipper.klipper import Klipper 14 | from components.moonraker.moonraker import Moonraker 15 | from components.webui_client.base_data import BaseWebClientConfig 16 | from core.logger import Logger 17 | from core.services.message_service import Message 18 | from core.types.color import Color 19 | from utils.config_utils import remove_config_section 20 | from utils.fs_utils import run_remove_routines 21 | from utils.instance_type import InstanceType 22 | from utils.instance_utils import get_instances 23 | 24 | 25 | def run_client_config_removal( 26 | client_config: BaseWebClientConfig, 27 | kl_instances: List[Klipper], 28 | mr_instances: List[Moonraker], 29 | ) -> Message: 30 | completion_msg = Message( 31 | title=f"{client_config.display_name} Removal Process completed", 32 | color=Color.GREEN, 33 | ) 34 | Logger.print_status(f"Removing {client_config.display_name} ...") 35 | if run_remove_routines(client_config.config_dir): 36 | completion_msg.text.append(f"● {client_config.display_name} removed") 37 | 38 | completion_msg = remove_moonraker_config_section( 39 | completion_msg, client_config, mr_instances 40 | ) 41 | 42 | completion_msg = remove_printer_config_section( 43 | completion_msg, client_config, kl_instances 44 | ) 45 | 46 | if completion_msg.text: 47 | completion_msg.text.insert(0, "The following actions were performed:") 48 | else: 49 | completion_msg.color = Color.YELLOW 50 | completion_msg.centered = True 51 | completion_msg.text = ["Nothing to remove."] 52 | 53 | return completion_msg 54 | 55 | 56 | def remove_cfg_symlink(client_config: BaseWebClientConfig, message: Message) -> Message: 57 | instances: List[Klipper] = get_instances(Klipper) 58 | kl_instances = [] 59 | for instance in instances: 60 | cfg = instance.base.cfg_dir.joinpath(client_config.config_filename) 61 | if run_remove_routines(cfg): 62 | kl_instances.append(instance) 63 | text = f"{client_config.display_name} removed from instance" 64 | return update_msg(kl_instances, message, text) 65 | 66 | 67 | def remove_printer_config_section( 68 | message: Message, client_config: BaseWebClientConfig, kl_instances: List[Klipper] 69 | ) -> Message: 70 | kl_section = client_config.config_section 71 | kl_instances = remove_config_section(kl_section, kl_instances) 72 | text = f"Klipper config section '{kl_section}' removed for instance" 73 | return update_msg(kl_instances, message, text) 74 | 75 | 76 | def remove_moonraker_config_section( 77 | message: Message, client_config: BaseWebClientConfig, mr_instances: List[Moonraker] 78 | ) -> Message: 79 | mr_section = f"update_manager {client_config.name}" 80 | mr_instances = remove_config_section(mr_section, mr_instances) 81 | text = f"Moonraker config section '{mr_section}' removed for instance" 82 | return update_msg(mr_instances, message, text) 83 | 84 | 85 | def update_msg(instances: List[InstanceType], message: Message, text: str) -> Message: 86 | if not instances: 87 | return message 88 | 89 | instance_names = [i.service_file_path.stem for i in instances] 90 | message.text.append(f"● {text}: {', '.join(instance_names)}") 91 | return message 92 | -------------------------------------------------------------------------------- /kiauh/components/webui_client/client_dialogs.py: -------------------------------------------------------------------------------- 1 | # ======================================================================= # 2 | # Copyright (C) 2020 - 2025 Dominik Willner <th33xitus@gmail.com> # 3 | # # 4 | # This file is part of KIAUH - Klipper Installation And Update Helper # 5 | # https://github.com/dw-0/kiauh # 6 | # # 7 | # This file may be distributed under the terms of the GNU GPLv3 license # 8 | # ======================================================================= # 9 | 10 | from typing import List 11 | 12 | from components.webui_client.base_data import BaseWebClient 13 | from core.logger import DialogType, Logger 14 | 15 | 16 | def print_moonraker_not_found_dialog(name: str) -> None: 17 | Logger.print_dialog( 18 | DialogType.WARNING, 19 | [ 20 | "No local Moonraker installation was found!", 21 | "\n\n", 22 | f"It is possible to install {name} without a local Moonraker installation. " 23 | "If you continue, you need to make sure, that Moonraker is installed on " 24 | f"another machine in your network. Otherwise {name} will NOT work " 25 | "correctly.", 26 | ], 27 | ) 28 | 29 | 30 | def print_client_already_installed_dialog(name: str) -> None: 31 | Logger.print_dialog( 32 | DialogType.WARNING, 33 | [ 34 | f"{name} seems to be already installed!", 35 | f"If you continue, your current {name} installation will be overwritten.", 36 | ], 37 | ) 38 | 39 | 40 | def print_client_port_select_dialog( 41 | name: str, port: int, ports_in_use: List[int] 42 | ) -> None: 43 | dialog_content: List[str] = [ 44 | f"Please select the port, {name} should be served on. If your are unsure " 45 | f"what to select, hit Enter to apply the suggested value of: {port}", 46 | "\n\n", 47 | f"In case you need {name} to be served on a specific port, you can set it " 48 | f"now. Make sure that the port is not already used by another application " 49 | f"on your system!", 50 | ] 51 | 52 | if ports_in_use: 53 | dialog_content.extend( 54 | [ 55 | "\n\n", 56 | "The following ports were found to be already in use:", 57 | *[f"● {p}" for p in ports_in_use if p != port], 58 | ] 59 | ) 60 | 61 | Logger.print_dialog(DialogType.CUSTOM, dialog_content) 62 | 63 | 64 | def print_install_client_config_dialog(client: BaseWebClient) -> None: 65 | name = client.display_name 66 | url = client.client_config.repo_url.replace(".git", "") 67 | Logger.print_dialog( 68 | DialogType.INFO, 69 | [ 70 | f"It is recommended to use special macros in order to have {name} fully " 71 | f"functional and working.", 72 | "\n\n", 73 | f"The recommended macros for {name} can be seen here:", 74 | url, 75 | "\n\n", 76 | "If you already use these macros skip this step. Otherwise you should " 77 | "consider to answer with 'Y' to download the recommended macros.", 78 | ], 79 | ) 80 | 81 | 82 | def print_ipv6_warning_dialog() -> None: 83 | Logger.print_dialog( 84 | DialogType.WARNING, 85 | [ 86 | "It looks like IPv6 is enabled on this system!", 87 | "This may cause issues with the installation of NGINX in the following " 88 | "steps! It is recommended to disable IPv6 on your system to avoid this issue.", 89 | "\n\n", 90 | "If you think this warning is a false alarm, and you are sure that " 91 | "IPv6 is disabled, you can continue with the installation.", 92 | ], 93 | ) 94 | -------------------------------------------------------------------------------- /kiauh/components/webui_client/fluidd_data.py: -------------------------------------------------------------------------------- 1 | # ======================================================================= # 2 | # Copyright (C) 2020 - 2025 Dominik Willner <th33xitus@gmail.com> # 3 | # # 4 | # This file is part of KIAUH - Klipper Installation And Update Helper # 5 | # https://github.com/dw-0/kiauh # 6 | # # 7 | # This file may be distributed under the terms of the GNU GPLv3 license # 8 | # ======================================================================= # 9 | 10 | from __future__ import annotations 11 | 12 | from dataclasses import dataclass 13 | from pathlib import Path 14 | 15 | from components.webui_client.base_data import ( 16 | BaseWebClient, 17 | BaseWebClientConfig, 18 | WebClientConfigType, 19 | WebClientType, 20 | ) 21 | from core.backup_manager import BACKUP_ROOT_DIR 22 | from core.constants import NGINX_SITES_AVAILABLE 23 | 24 | 25 | @dataclass() 26 | class FluiddConfigWeb(BaseWebClientConfig): 27 | client_config: WebClientConfigType = WebClientConfigType.FLUIDD 28 | name: str = client_config.value 29 | display_name: str = name.title() 30 | config_dir: Path = Path.home().joinpath("fluidd-config") 31 | config_filename: str = "fluidd.cfg" 32 | config_section: str = f"include {config_filename}" 33 | backup_dir: Path = BACKUP_ROOT_DIR.joinpath("fluidd-config-backups") 34 | repo_url: str = "https://github.com/fluidd-core/fluidd-config.git" 35 | 36 | 37 | @dataclass() 38 | class FluiddData(BaseWebClient): 39 | BASE_DL_URL = "https://github.com/fluidd-core/fluidd/releases" 40 | 41 | client: WebClientType = WebClientType.FLUIDD 42 | name: str = client.value 43 | display_name: str = name.capitalize() 44 | client_dir: Path = Path.home().joinpath("fluidd") 45 | config_file: Path = client_dir.joinpath("config.json") 46 | backup_dir: Path = BACKUP_ROOT_DIR.joinpath("fluidd-backups") 47 | repo_path: str = "fluidd-core/fluidd" 48 | nginx_config: Path = NGINX_SITES_AVAILABLE.joinpath("fluidd") 49 | nginx_access_log: Path = Path("/var/log/nginx/fluidd-access.log") 50 | nginx_error_log: Path = Path("/var/log/nginx/fluidd-error.log") 51 | client_config: BaseWebClientConfig = None 52 | download_url: str | None = None 53 | 54 | def __post_init__(self): 55 | from components.webui_client.client_utils import get_download_url 56 | 57 | self.client_config = FluiddConfigWeb() 58 | self.download_url = get_download_url(self.BASE_DL_URL, self) 59 | -------------------------------------------------------------------------------- /kiauh/components/webui_client/mainsail_data.py: -------------------------------------------------------------------------------- 1 | # ======================================================================= # 2 | # Copyright (C) 2020 - 2025 Dominik Willner <th33xitus@gmail.com> # 3 | # # 4 | # This file is part of KIAUH - Klipper Installation And Update Helper # 5 | # https://github.com/dw-0/kiauh # 6 | # # 7 | # This file may be distributed under the terms of the GNU GPLv3 license # 8 | # ======================================================================= # 9 | 10 | from __future__ import annotations 11 | 12 | from dataclasses import dataclass 13 | from pathlib import Path 14 | 15 | from components.webui_client.base_data import ( 16 | BaseWebClient, 17 | BaseWebClientConfig, 18 | WebClientConfigType, 19 | WebClientType, 20 | ) 21 | from core.backup_manager import BACKUP_ROOT_DIR 22 | from core.constants import NGINX_SITES_AVAILABLE 23 | 24 | 25 | @dataclass() 26 | class MainsailConfigWeb(BaseWebClientConfig): 27 | client_config: WebClientConfigType = WebClientConfigType.MAINSAIL 28 | name: str = client_config.value 29 | display_name: str = name.title() 30 | config_dir: Path = Path.home().joinpath("mainsail-config") 31 | config_filename: str = "mainsail.cfg" 32 | config_section: str = f"include {config_filename}" 33 | backup_dir: Path = BACKUP_ROOT_DIR.joinpath("mainsail-config-backups") 34 | repo_url: str = "https://github.com/mainsail-crew/mainsail-config.git" 35 | 36 | 37 | @dataclass() 38 | class MainsailData(BaseWebClient): 39 | BASE_DL_URL: str = "https://github.com/mainsail-crew/mainsail/releases" 40 | 41 | client: WebClientType = WebClientType.MAINSAIL 42 | name: str = WebClientType.MAINSAIL.value 43 | display_name: str = name.capitalize() 44 | client_dir: Path = Path.home().joinpath("mainsail") 45 | config_file: Path = client_dir.joinpath("config.json") 46 | backup_dir: Path = BACKUP_ROOT_DIR.joinpath("mainsail-backups") 47 | repo_path: str = "mainsail-crew/mainsail" 48 | nginx_config: Path = NGINX_SITES_AVAILABLE.joinpath("mainsail") 49 | nginx_access_log: Path = Path("/var/log/nginx/mainsail-access.log") 50 | nginx_error_log: Path = Path("/var/log/nginx/mainsail-error.log") 51 | client_config: BaseWebClientConfig = None 52 | download_url: str | None = None 53 | 54 | def __post_init__(self): 55 | from components.webui_client.client_utils import get_download_url 56 | 57 | self.client_config = MainsailConfigWeb() 58 | self.download_url = get_download_url(self.BASE_DL_URL, self) 59 | -------------------------------------------------------------------------------- /kiauh/components/webui_client/menus/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dw-0/kiauh/33113e72e9a45ac1a4020d409b5932e60e4c77eb/kiauh/components/webui_client/menus/__init__.py -------------------------------------------------------------------------------- /kiauh/core/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dw-0/kiauh/33113e72e9a45ac1a4020d409b5932e60e4c77eb/kiauh/core/__init__.py -------------------------------------------------------------------------------- /kiauh/core/backup_manager/__init__.py: -------------------------------------------------------------------------------- 1 | # ======================================================================= # 2 | # Copyright (C) 2020 - 2025 Dominik Willner <th33xitus@gmail.com> # 3 | # # 4 | # This file is part of KIAUH - Klipper Installation And Update Helper # 5 | # https://github.com/dw-0/kiauh # 6 | # # 7 | # This file may be distributed under the terms of the GNU GPLv3 license # 8 | # ======================================================================= # 9 | 10 | from pathlib import Path 11 | 12 | BACKUP_ROOT_DIR = Path.home().joinpath("kiauh-backups") 13 | -------------------------------------------------------------------------------- /kiauh/core/constants.py: -------------------------------------------------------------------------------- 1 | # ======================================================================= # 2 | # Copyright (C) 2020 - 2025 Dominik Willner <th33xitus@gmail.com> # 3 | # # 4 | # This file is part of KIAUH - Klipper Installation And Update Helper # 5 | # https://github.com/dw-0/kiauh # 6 | # # 7 | # This file may be distributed under the terms of the GNU GPLv3 license # 8 | # ======================================================================= # 9 | 10 | import os 11 | import pwd 12 | from pathlib import Path 13 | 14 | from core.backup_manager import BACKUP_ROOT_DIR 15 | 16 | # global dependencies 17 | GLOBAL_DEPS = ["git", "wget", "curl", "unzip", "dfu-util", "python3-virtualenv"] 18 | 19 | # strings 20 | INVALID_CHOICE = "Invalid choice. Please select a valid value." 21 | 22 | # current user 23 | CURRENT_USER = pwd.getpwuid(os.getuid())[0] 24 | 25 | # dirs 26 | SYSTEMD = Path("/etc/systemd/system") 27 | PRINTER_DATA_BACKUP_DIR = BACKUP_ROOT_DIR.joinpath("printer-data-backups") 28 | NGINX_SITES_AVAILABLE = Path("/etc/nginx/sites-available") 29 | NGINX_SITES_ENABLED = Path("/etc/nginx/sites-enabled") 30 | NGINX_CONFD = Path("/etc/nginx/conf.d") 31 | -------------------------------------------------------------------------------- /kiauh/core/decorators.py: -------------------------------------------------------------------------------- 1 | # ======================================================================= # 2 | # Copyright (C) 2020 - 2025 Dominik Willner <th33xitus@gmail.com> # 3 | # # 4 | # This file is part of KIAUH - Klipper Installation And Update Helper # 5 | # https://github.com/dw-0/kiauh # 6 | # # 7 | # This file may be distributed under the terms of the GNU GPLv3 license # 8 | # ======================================================================= # 9 | from __future__ import annotations 10 | 11 | import warnings 12 | from typing import Callable 13 | 14 | 15 | def deprecated(info: str = "", replaced_by: Callable | None = None) -> Callable: 16 | def decorator(func) -> Callable: 17 | def wrapper(*args, **kwargs): 18 | msg = f"{info}{replaced_by.__name__ if replaced_by else ''}" 19 | warnings.warn(msg, category=DeprecationWarning, stacklevel=2) 20 | return func(*args, **kwargs) 21 | 22 | return wrapper 23 | 24 | return decorator 25 | -------------------------------------------------------------------------------- /kiauh/core/instance_manager/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dw-0/kiauh/33113e72e9a45ac1a4020d409b5932e60e4c77eb/kiauh/core/instance_manager/__init__.py -------------------------------------------------------------------------------- /kiauh/core/instance_manager/base_instance.py: -------------------------------------------------------------------------------- 1 | # ======================================================================= # 2 | # Copyright (C) 2020 - 2025 Dominik Willner <th33xitus@gmail.com> # 3 | # # 4 | # This file is part of KIAUH - Klipper Installation And Update Helper # 5 | # https://github.com/dw-0/kiauh # 6 | # # 7 | # This file may be distributed under the terms of the GNU GPLv3 license # 8 | # ======================================================================= # 9 | 10 | from __future__ import annotations 11 | 12 | import re 13 | from dataclasses import dataclass, field 14 | from pathlib import Path 15 | from typing import List 16 | 17 | from utils.fs_utils import get_data_dir 18 | 19 | SUFFIX_BLACKLIST: List[str] = ["None", "mcu", "obico", "bambu", "companion"] 20 | 21 | 22 | @dataclass(repr=True) 23 | class BaseInstance: 24 | instance_type: type 25 | suffix: str 26 | log_file_name: str | None = None 27 | data_dir: Path = field(init=False) 28 | base_folders: List[Path] = field(init=False) 29 | cfg_dir: Path = field(init=False) 30 | log_dir: Path = field(init=False) 31 | gcodes_dir: Path = field(init=False) 32 | comms_dir: Path = field(init=False) 33 | sysd_dir: Path = field(init=False) 34 | is_legacy_instance: bool = field(init=False) 35 | 36 | def __post_init__(self): 37 | self.data_dir = get_data_dir(self.instance_type, self.suffix) 38 | # the following attributes require the data_dir to be set 39 | self.cfg_dir = self.data_dir.joinpath("config") 40 | self.log_dir = self.data_dir.joinpath("logs") 41 | self.gcodes_dir = self.data_dir.joinpath("gcodes") 42 | self.comms_dir = self.data_dir.joinpath("comms") 43 | self.sysd_dir = self.data_dir.joinpath("systemd") 44 | self.is_legacy_instance = self._set_is_legacy_instance() 45 | self.base_folders = [ 46 | self.data_dir, 47 | self.cfg_dir, 48 | self.log_dir, 49 | self.gcodes_dir, 50 | self.comms_dir, 51 | self.sysd_dir, 52 | ] 53 | 54 | def _set_is_legacy_instance(self) -> bool: 55 | legacy_pattern = r"^(?!printer)(.+)_data" 56 | match = re.search(legacy_pattern, self.data_dir.name) 57 | 58 | return True if (match and self.suffix != "") else False 59 | -------------------------------------------------------------------------------- /kiauh/core/menus/__init__.py: -------------------------------------------------------------------------------- 1 | # ======================================================================= # 2 | # Copyright (C) 2020 - 2025 Dominik Willner <th33xitus@gmail.com> # 3 | # # 4 | # This file is part of KIAUH - Klipper Installation And Update Helper # 5 | # https://github.com/dw-0/kiauh # 6 | # # 7 | # This file may be distributed under the terms of the GNU GPLv3 license # 8 | # ======================================================================= # 9 | from __future__ import annotations 10 | 11 | from dataclasses import dataclass 12 | from enum import Enum 13 | from typing import Any, Callable, Type 14 | 15 | 16 | @dataclass 17 | class Option: 18 | """ 19 | Represents a menu option. 20 | :param method: Method that will be used to call the menu option 21 | :param opt_index: Can be used to pass the user input to the menu option 22 | :param opt_data: Can be used to pass any additional data to the menu option 23 | """ 24 | 25 | def __repr__(self): 26 | return f"Option(method={self.method.__name__}, opt_index={self.opt_index}, opt_data={self.opt_data})" 27 | 28 | method: Type[Callable] 29 | opt_index: str = "" 30 | opt_data: Any = None 31 | 32 | 33 | class FooterType(Enum): 34 | QUIT = "QUIT" 35 | BACK = "BACK" 36 | BACK_HELP = "BACK_HELP" 37 | BLANK = "BLANK" 38 | -------------------------------------------------------------------------------- /kiauh/core/menus/repo_select_menu.py: -------------------------------------------------------------------------------- 1 | # ======================================================================= # 2 | # Copyright (C) 2020 - 2025 Dominik Willner <th33xitus@gmail.com> # 3 | # # 4 | # This file is part of KIAUH - Klipper Installation And Update Helper # 5 | # https://github.com/dw-0/kiauh # 6 | # # 7 | # This file may be distributed under the terms of the GNU GPLv3 license # 8 | # ======================================================================= # 9 | from __future__ import annotations 10 | 11 | from typing import List, Literal, Type 12 | 13 | from core.logger import Logger 14 | from core.menus import Option 15 | from core.menus.base_menu import BaseMenu 16 | from core.settings.kiauh_settings import KiauhSettings, Repository 17 | from core.types.color import Color 18 | from procedures.switch_repo import run_switch_repo_routine 19 | 20 | 21 | class RepoSelectMenu(BaseMenu): 22 | def __init__( 23 | self, 24 | name: Literal["klipper", "moonraker"], 25 | repos: List[Repository], 26 | previous_menu: Type[BaseMenu] | None = None, 27 | ) -> None: 28 | super().__init__() 29 | self.title_color = Color.CYAN 30 | self.previous_menu = previous_menu 31 | self.settings = KiauhSettings() 32 | self.input_label_txt = "Select repository" 33 | self.name = name 34 | self.repos = repos 35 | 36 | if self.name == "klipper": 37 | self.title = "Klipper Repository Selection Menu" 38 | 39 | elif self.name == "moonraker": 40 | self.title = "Moonraker Repository Selection Menu" 41 | 42 | def set_previous_menu(self, previous_menu: Type[BaseMenu] | None) -> None: 43 | from core.menus.settings_menu import SettingsMenu 44 | 45 | self.previous_menu = ( 46 | previous_menu if previous_menu is not None else SettingsMenu 47 | ) 48 | 49 | def set_options(self) -> None: 50 | self.options = {} 51 | 52 | if not self.repos: 53 | return 54 | 55 | for idx, repo in enumerate(self.repos, start=1): 56 | self.options[str(idx)] = Option( 57 | method=self.select_repository, opt_data=repo 58 | ) 59 | 60 | def print_menu(self) -> None: 61 | menu = "╟───────────────────────────────────────────────────────╢\n" 62 | menu += "║ Available Repositories: ║\n" 63 | menu += "╟───────────────────────────────────────────────────────╢\n" 64 | 65 | for idx, repo in enumerate(self.repos, start=1): 66 | url = f"● Repo: {repo.url.replace('.git', '')}" 67 | branch = f"└► Branch: {repo.branch}" 68 | menu += f"║ {idx}) {Color.apply(url, Color.CYAN):<59} ║\n" 69 | menu += f"║ {Color.apply(branch, Color.CYAN):<59} ║\n" 70 | 71 | menu += "╟───────────────────────────────────────────────────────╢\n" 72 | print(menu, end="") 73 | 74 | def select_repository(self, **kwargs) -> None: 75 | repo: Repository = kwargs.get("opt_data") 76 | Logger.print_status( 77 | f"Switching to {self.name.capitalize()}'s new source repository ..." 78 | ) 79 | run_switch_repo_routine(self.name, repo.url, repo.branch) 80 | -------------------------------------------------------------------------------- /kiauh/core/services/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dw-0/kiauh/33113e72e9a45ac1a4020d409b5932e60e4c77eb/kiauh/core/services/__init__.py -------------------------------------------------------------------------------- /kiauh/core/services/message_service.py: -------------------------------------------------------------------------------- 1 | # ======================================================================= # 2 | # Copyright (C) 2020 - 2025 Dominik Willner <th33xitus@gmail.com> # 3 | # # 4 | # This file is part of KIAUH - Klipper Installation And Update Helper # 5 | # https://github.com/dw-0/kiauh # 6 | # # 7 | # This file may be distributed under the terms of the GNU GPLv3 license # 8 | # ======================================================================= # 9 | from __future__ import annotations 10 | 11 | from dataclasses import dataclass, field 12 | from typing import List 13 | 14 | from core.logger import DialogType, Logger 15 | from core.types.color import Color 16 | 17 | 18 | @dataclass() 19 | class Message: 20 | title: str = field(default="") 21 | text: List[str] = field(default_factory=list) 22 | color: Color = field(default=Color.WHITE) 23 | centered: bool = field(default=False) 24 | 25 | 26 | class MessageService: 27 | __cls_instance = None 28 | __message: Message | None 29 | 30 | def __new__(cls) -> "MessageService": 31 | if cls.__cls_instance is None: 32 | cls.__cls_instance = super(MessageService, cls).__new__(cls) 33 | return cls.__cls_instance 34 | 35 | def __init__(self) -> None: 36 | if not hasattr(self, "__initialized"): 37 | self.__initialized = False 38 | if self.__initialized: 39 | return 40 | self.__initialized = True 41 | self.__message = None 42 | 43 | def set_message(self, message: Message) -> None: 44 | self.__message = message 45 | 46 | def display_message(self) -> None: 47 | if self.__message is None: 48 | return 49 | 50 | Logger.print_dialog( 51 | title=DialogType.CUSTOM, 52 | content=self.__message.text, 53 | custom_title=self.__message.title, 54 | custom_color=self.__message.color, 55 | center_content=self.__message.centered, 56 | ) 57 | 58 | self.__clear_message() 59 | 60 | def __clear_message(self) -> None: 61 | self.__message = None 62 | -------------------------------------------------------------------------------- /kiauh/core/settings/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dw-0/kiauh/33113e72e9a45ac1a4020d409b5932e60e4c77eb/kiauh/core/settings/__init__.py -------------------------------------------------------------------------------- /kiauh/core/spinner.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import threading 3 | import time 4 | from typing import List, Literal 5 | 6 | from core.types.color import Color 7 | 8 | SpinnerColor = Literal["white", "red", "green", "yellow"] 9 | 10 | 11 | class Spinner: 12 | def __init__( 13 | self, 14 | message: str = "Loading", 15 | interval: float = 0.2, 16 | ) -> None: 17 | self.message = f"{message} ..." 18 | self.interval = interval 19 | self._stop_event = threading.Event() 20 | self._thread = threading.Thread(target=self._animate) 21 | 22 | def _animate(self) -> None: 23 | animation: List[str] = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"] 24 | while not self._stop_event.is_set(): 25 | for char in animation: 26 | sys.stdout.write(f"\r{Color.GREEN}{char}{Color.RST} {self.message}") 27 | sys.stdout.flush() 28 | time.sleep(self.interval) 29 | if self._stop_event.is_set(): 30 | break 31 | sys.stdout.write("\r" + " " * (len(self.message) + 1) + "\r") 32 | sys.stdout.flush() 33 | 34 | def start(self) -> None: 35 | self._stop_event.clear() 36 | if not self._thread.is_alive(): 37 | self._thread = threading.Thread(target=self._animate) 38 | self._thread.start() 39 | 40 | def stop(self) -> None: 41 | self._stop_event.set() 42 | self._thread.join() 43 | -------------------------------------------------------------------------------- /kiauh/core/submodules/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dw-0/kiauh/33113e72e9a45ac1a4020d409b5932e60e4c77eb/kiauh/core/submodules/__init__.py -------------------------------------------------------------------------------- /kiauh/core/submodules/simple_config_parser/.editorconfig: -------------------------------------------------------------------------------- 1 | # see https://editorconfig.org/ 2 | root = true 3 | 4 | [*] 5 | end_of_line = lf 6 | trim_trailing_whitespace = true 7 | indent_style = space 8 | insert_final_newline = true 9 | indent_size = 4 10 | charset = utf-8 11 | 12 | [*.py] 13 | max_line_length = 88 14 | -------------------------------------------------------------------------------- /kiauh/core/submodules/simple_config_parser/.gitignore: -------------------------------------------------------------------------------- 1 | *.py[cod] 2 | *.pyc 3 | __pycache__ 4 | .pytest_cache/ 5 | 6 | .idea/ 7 | .vscode/ 8 | 9 | .venv*/ 10 | venv*/ 11 | 12 | .coverage 13 | htmlcov/ 14 | -------------------------------------------------------------------------------- /kiauh/core/submodules/simple_config_parser/README.md: -------------------------------------------------------------------------------- 1 | # Simple Config Parser 2 | 3 | A custom config parser inspired by Python's configparser module. 4 | Specialized for handling Klipper style config files. 5 | 6 | --- 7 | 8 | ### When parsing a config file, it will be split into the following elements: 9 | - Header: All lines before the first section 10 | - Section: A section is defined by a line starting with a `[` and ending with a `]` 11 | - Option: A line starting with a word, followed by a `:` or `=` and a value 12 | - Option Block: A line starting with a word, followed by a `:` or `=` and a newline 13 | - Comment: A line starting with a `#` or `;` 14 | - Blank: A line containing only whitespace characters 15 | 16 | --- 17 | 18 | ### Internally, the config is stored as a dictionary of sections, each containing a header and a list of elements: 19 | ```python 20 | config = { 21 | "section_name": { 22 | "header": "[section_name]\n", 23 | "elements": [ 24 | { 25 | "type": "comment", 26 | "content": "# This is a comment\n" 27 | }, 28 | { 29 | "type": "option", 30 | "name": "option1", 31 | "value": "value1", 32 | "raw": "option1: value1\n" 33 | }, 34 | { 35 | "type": "blank", 36 | "content": "\n" 37 | }, 38 | { 39 | "type": "option_block", 40 | "name": "option2", 41 | "value": [ 42 | "value2", 43 | "value3" 44 | ], 45 | "raw": "option2:" 46 | } 47 | ] 48 | } 49 | } 50 | ``` 51 | -------------------------------------------------------------------------------- /kiauh/core/submodules/simple_config_parser/pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "simple-config-parser" 3 | version = "0.0.1" 4 | description = "A simple config parser for Python" 5 | authors = [ 6 | {name = "Dominik Willner", email = "th33xitus@gmail.com"}, 7 | ] 8 | readme = "README.md" 9 | license = {text = "GPL-3.0-only"} 10 | requires-python = ">=3.8" 11 | 12 | [project.urls] 13 | homepage = "https://github.com/dw-0/simple-config-parser" 14 | repository = "https://github.com/dw-0/simple-config-parser" 15 | documentation = "https://github.com/dw-0/simple-config-parser" 16 | 17 | [project.optional-dependencies] 18 | dev=["ruff"] 19 | 20 | [tool.ruff] 21 | required-version = ">=0.3.4" 22 | respect-gitignore = true 23 | exclude = [".git",".github", "./docs"] 24 | line-length = 88 25 | indent-width = 4 26 | output-format = "full" 27 | 28 | [tool.ruff.format] 29 | indent-style = "space" 30 | line-ending = "lf" 31 | quote-style = "double" 32 | 33 | [tool.ruff.lint] 34 | extend-select = ["I"] 35 | 36 | [tool.pytest.ini_options] 37 | minversion = "8.2.1" 38 | testpaths = ["tests/**/*.py"] 39 | addopts = "-svvv --cov --cov-config=pyproject.toml --cov-report=html" 40 | 41 | [tool.coverage.run] 42 | branch = true 43 | source = ["src.simple_config_parser"] 44 | 45 | [tool.coverage.report] 46 | # Regexes for lines to exclude from consideration 47 | exclude_also = [ 48 | # Don't complain about missing debug-only code: 49 | "def __repr__", 50 | "if self\\.debug", 51 | 52 | # Don't complain if tests don't hit defensive assertion code: 53 | "raise AssertionError", 54 | "raise NotImplementedError", 55 | 56 | # Don't complain if non-runnable code isn't run: 57 | "if 0:", 58 | "if __name__ == .__main__.:", 59 | 60 | # Don't complain about abstract methods, they aren't run: 61 | "@(abc\\.)?abstractmethod", 62 | ] 63 | 64 | [tool.coverage.html] 65 | title = "SimpleConfigParser Coverage Report" 66 | directory = "htmlcov" 67 | -------------------------------------------------------------------------------- /kiauh/core/submodules/simple_config_parser/requirements-dev.txt: -------------------------------------------------------------------------------- 1 | ruff >= 0.3.4 2 | pytest >= 8.2.1 3 | pytest-cov >= 5.0.0 4 | -------------------------------------------------------------------------------- /kiauh/core/submodules/simple_config_parser/src/simple_config_parser/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dw-0/kiauh/33113e72e9a45ac1a4020d409b5932e60e4c77eb/kiauh/core/submodules/simple_config_parser/src/simple_config_parser/__init__.py -------------------------------------------------------------------------------- /kiauh/core/submodules/simple_config_parser/src/simple_config_parser/constants.py: -------------------------------------------------------------------------------- 1 | # ======================================================================= # 2 | # Copyright (C) 2024 Dominik Willner <th33xitus@gmail.com> # 3 | # # 4 | # https://github.com/dw-0/simple-config-parser # 5 | # # 6 | # This file may be distributed under the terms of the GNU GPLv3 license # 7 | # ======================================================================= # 8 | import re 9 | from enum import Enum 10 | 11 | # definition of section line: 12 | # - then line MUST start with an opening square bracket - it is the first section marker 13 | # - the section marker MUST be followed by at least one character - it is the section name 14 | # - the section name MUST be followed by a closing square bracket - it is the second section marker 15 | # - the second section marker MAY be followed by any amount of whitespace characters 16 | # - the second section marker MAY be followed by a # or ; - it is the comment marker 17 | # - the inline comment MAY be of any length and character 18 | SECTION_RE = re.compile(r"^\[(\S.*\S|\S)]\s*([#;].*)?quot;) 19 | 20 | # definition of option line: 21 | # - the line MUST start with a word - it is the option name 22 | # - the option name MUST be followed by a colon or an equal sign - it is the separator 23 | # - the separator MUST be followed by a value 24 | # - the separator MAY have any amount of leading or trailing whitespaces 25 | # - the separator MUST NOT be directly followed by a colon or equal sign 26 | # - the value MAY be of any length and character 27 | # - the value MAY contain any amount of trailing whitespaces 28 | # - the value MAY be followed by a # or ; - it is the comment marker 29 | # - the inline comment MAY be of any length and character 30 | OPTION_RE = re.compile(r"^([^;#:=\s]+)\s?[:=]\s*([^;#:=\s][^;#]*?)\s*([#;].*)?quot;) 31 | # definition of options block start line: 32 | # - the line MUST start with a word - it is the option name 33 | # - the option name MUST be followed by a colon or an equal sign - it is the separator 34 | # - the separator MUST NOT be followed by a value 35 | # - the separator MAY have any amount of leading or trailing whitespaces 36 | # - the separator MUST NOT be directly followed by a colon or equal sign 37 | # - the separator MAY be followed by a # or ; - it is the comment marker 38 | # - the inline comment MAY be of any length and character 39 | OPTIONS_BLOCK_START_RE = re.compile(r"^([^;#:=\s]+)\s*[:=]\s*([#;].*)?quot;) 40 | 41 | # definition of comment line: 42 | # - the line MAY start with any amount of whitespace characters 43 | # - the line MUST contain a # or ; - it is the comment marker 44 | # - the comment marker MAY be followed by any amount of whitespace characters 45 | # - the comment MAY be of any length and character 46 | LINE_COMMENT_RE = re.compile(r"^\s*[#;].*") 47 | 48 | # definition of empty line: 49 | # - the line MUST contain only whitespace characters 50 | EMPTY_LINE_RE = re.compile(r"^\s*quot;) 51 | 52 | BOOLEAN_STATES = { 53 | "1": True, 54 | "yes": True, 55 | "true": True, 56 | "on": True, 57 | "0": False, 58 | "no": False, 59 | "false": False, 60 | "off": False, 61 | } 62 | 63 | HEADER_IDENT = "#_header" 64 | 65 | INDENT = " " * 4 66 | 67 | class LineType(Enum): 68 | OPTION = "option" 69 | OPTION_BLOCK = "option_block" 70 | COMMENT = "comment" 71 | BLANK = "blank" 72 | -------------------------------------------------------------------------------- /kiauh/core/submodules/simple_config_parser/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dw-0/kiauh/33113e72e9a45ac1a4020d409b5932e60e4c77eb/kiauh/core/submodules/simple_config_parser/tests/__init__.py -------------------------------------------------------------------------------- /kiauh/core/submodules/simple_config_parser/tests/assets/test_config_1.cfg: -------------------------------------------------------------------------------- 1 | # a comment at the very top 2 | # should be treated as the file header 3 | 4 | # up to the first section, including all blank lines 5 | 6 | [section_1] 7 | option_1: value_1 8 | option_1_1: True # this is a boolean 9 | option_1_2: 5 ; this is an integer 10 | option_1_3: 1.123 #;this is a float 11 | 12 | [section_2] ; comment 13 | option_2: value_2 14 | 15 | ; comment 16 | 17 | [section_3] 18 | option_3: value_3 # comment 19 | 20 | [section_4] 21 | # comment 22 | option_4: value_4 23 | 24 | [section number 5] 25 | #option_5: value_5 26 | option_5 = this.is.value-5 27 | multi_option: 28 | # these are multi-line values 29 | value_5_1 30 | value_5_2 ; here is a comment 31 | value_5_3 32 | option_5_1: value_5_1 33 | -------------------------------------------------------------------------------- /kiauh/core/submodules/simple_config_parser/tests/assets/test_config_2.cfg: -------------------------------------------------------------------------------- 1 | # a comment at the very top 2 | # should be treated as the file header 3 | 4 | # up to the first section, including all blank lines 5 | 6 | [section_1] 7 | option_1: value_1 8 | option_1_1: True # this is a boolean 9 | option_1_2: 5 ; this is an integer 10 | option_1_3: 1.123 #;this is a float 11 | 12 | [section_2] ; comment 13 | option_2: value_2 14 | 15 | ; comment 16 | 17 | [section_3] 18 | option_3: value_3 # comment 19 | 20 | [section_4] 21 | # comment 22 | option_4: value_4 23 | 24 | [section number 5] 25 | #option_5: value_5 26 | option_5 = this.is.value-5 27 | multi_option: 28 | # these are multi-line values 29 | value_5_1 30 | value_5_2 ; here is a comment 31 | value_5_3 32 | option_5_1: value_5_1 33 | # config ending with a comment 34 | -------------------------------------------------------------------------------- /kiauh/core/submodules/simple_config_parser/tests/assets/test_config_3.cfg: -------------------------------------------------------------------------------- 1 | # a comment at the very top 2 | # should be treated as the file header 3 | 4 | # up to the first section, including all blank lines 5 | 6 | [section_1] 7 | option_1: value_1 8 | option_1_1: True # this is a boolean 9 | option_1_2: 5 ; this is an integer 10 | option_1_3: 1.123 #;this is a float 11 | 12 | [section_2] ; comment 13 | option_2: value_2 14 | 15 | ; comment 16 | 17 | [section_3] 18 | option_3: value_3 # comment 19 | 20 | [section_4] 21 | # comment 22 | option_4: value_4 23 | 24 | [section number 5] 25 | #option_5: value_5 26 | option_5 = this.is.value-5 27 | multi_option: 28 | # these are multi-line values 29 | value_5_1 30 | value_5_2 ; here is a comment 31 | value_5_3 32 | option_5_1: value_5_1 33 | 34 | [gcode_macro M117] 35 | rename_existing: M117.1 36 | gcode: 37 | {% if rawparams %} 38 | {% set escaped_msg = rawparams.split(';', 1)[0].split('\x23', 1)[0]|replace('"', '\\"') %} 39 | SET_DISPLAY_TEXT MSG="{escaped_msg}" 40 | RESPOND TYPE=command MSG="{escaped_msg}" 41 | {% else %} 42 | SET_DISPLAY_TEXT 43 | {% endif %} 44 | 45 | # SDCard 'looping' (aka Marlin M808 commands) support 46 | # 47 | # Support SDCard looping 48 | [sdcard_loop] 49 | [gcode_macro M486] 50 | gcode: 51 | # Parameters known to M486 are as follows: 52 | # [C<flag>] Cancel the current object 53 | # [P<index>] Cancel the object with the given index 54 | # [S<index>] Set the index of the current object. 55 | # If the object with the given index has been canceled, this will cause 56 | # the firmware to skip to the next object. The value -1 is used to 57 | # indicate something that isn’t an object and shouldn’t be skipped. 58 | # [T<count>] Reset the state and set the number of objects 59 | # [U<index>] Un-cancel the object with the given index. This command will be 60 | # ignored if the object has already been skipped 61 | 62 | {% if 'exclude_object' not in printer %} 63 | {action_raise_error("[exclude_object] is not enabled")} 64 | {% endif %} 65 | 66 | {% if 'T' in params %} 67 | EXCLUDE_OBJECT RESET=1 68 | 69 | {% for i in range(params.T | int) %} 70 | EXCLUDE_OBJECT_DEFINE NAME={i} 71 | {% endfor %} 72 | {% endif %} 73 | 74 | {% if 'C' in params %} 75 | EXCLUDE_OBJECT CURRENT=1 76 | {% endif %} 77 | 78 | {% if 'P' in params %} 79 | EXCLUDE_OBJECT NAME={params.P} 80 | {% endif %} 81 | 82 | {% if 'S' in params %} 83 | {% if params.S == '-1' %} 84 | {% if printer.exclude_object.current_object %} 85 | EXCLUDE_OBJECT_END NAME={printer.exclude_object.current_object} 86 | {% endif %} 87 | {% else %} 88 | EXCLUDE_OBJECT_START NAME={params.S} 89 | {% endif %} 90 | {% endif %} 91 | 92 | {% if 'U' in params %} 93 | EXCLUDE_OBJECT RESET=1 NAME={params.U} 94 | {% endif %} 95 | -------------------------------------------------------------------------------- /kiauh/core/submodules/simple_config_parser/tests/assets/write_tests/add_option/expected.cfg: -------------------------------------------------------------------------------- 1 | [section_1] 2 | # comment 3 | option_1: value_1 4 | option_2: value_2 ; comment 5 | new_option: new_value 6 | 7 | [section_2] 8 | option_3: value_3 9 | -------------------------------------------------------------------------------- /kiauh/core/submodules/simple_config_parser/tests/assets/write_tests/add_option/input.cfg: -------------------------------------------------------------------------------- 1 | [section_1] 2 | # comment 3 | option_1: value_1 4 | option_2: value_2 ; comment 5 | 6 | [section_2] 7 | option_3: value_3 8 | -------------------------------------------------------------------------------- /kiauh/core/submodules/simple_config_parser/tests/assets/write_tests/remove_option/expected.cfg: -------------------------------------------------------------------------------- 1 | [section_1] 2 | # comment 3 | option_1: value_1 4 | option_2: value_2 ; comment 5 | 6 | [section_2] 7 | option_3: value_3 8 | -------------------------------------------------------------------------------- /kiauh/core/submodules/simple_config_parser/tests/assets/write_tests/remove_option/input.cfg: -------------------------------------------------------------------------------- 1 | [section_1] 2 | # comment 3 | option_1: value_1 4 | option_to_remove: value_to_remove 5 | option_2: value_2 ; comment 6 | 7 | [section_2] 8 | option_3: value_3 9 | -------------------------------------------------------------------------------- /kiauh/core/submodules/simple_config_parser/tests/assets/write_tests/remove_section/expected.cfg: -------------------------------------------------------------------------------- 1 | [section_1] 2 | option_1: value_1 3 | option_2: value_2 4 | 5 | # comment 6 | [section_2] 7 | option_5: value_5 8 | -------------------------------------------------------------------------------- /kiauh/core/submodules/simple_config_parser/tests/assets/write_tests/remove_section/input.cfg: -------------------------------------------------------------------------------- 1 | [section_1] 2 | option_1: value_1 3 | option_2: value_2 4 | 5 | # comment 6 | [section_to_remove] 7 | option_3: value_3 8 | option_4: value_4 9 | 10 | [section_2] 11 | option_5: value_5 12 | -------------------------------------------------------------------------------- /kiauh/core/submodules/simple_config_parser/tests/line_matching/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dw-0/kiauh/33113e72e9a45ac1a4020d409b5932e60e4c77eb/kiauh/core/submodules/simple_config_parser/tests/line_matching/__init__.py -------------------------------------------------------------------------------- /kiauh/core/submodules/simple_config_parser/tests/line_matching/match_empty_line/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dw-0/kiauh/33113e72e9a45ac1a4020d409b5932e60e4c77eb/kiauh/core/submodules/simple_config_parser/tests/line_matching/match_empty_line/__init__.py -------------------------------------------------------------------------------- /kiauh/core/submodules/simple_config_parser/tests/line_matching/match_empty_line/test_data/matching_data.txt: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /kiauh/core/submodules/simple_config_parser/tests/line_matching/match_empty_line/test_data/non_matching_data.txt: -------------------------------------------------------------------------------- 1 | not_empty 2 | [also_not_empty] 3 | # 4 | ; 5 | ; 6 | # 7 | option: value 8 | -------------------------------------------------------------------------------- /kiauh/core/submodules/simple_config_parser/tests/line_matching/match_empty_line/test_match_empty_line.py: -------------------------------------------------------------------------------- 1 | # ======================================================================= # 2 | # Copyright (C) 2024 Dominik Willner <th33xitus@gmail.com> # 3 | # # 4 | # https://github.com/dw-0/simple-config-parser # 5 | # # 6 | # This file may be distributed under the terms of the GNU GPLv3 license # 7 | # ======================================================================= # 8 | 9 | from pathlib import Path 10 | 11 | import pytest 12 | 13 | from src.simple_config_parser.simple_config_parser import SimpleConfigParser 14 | from tests.utils import load_testdata_from_file 15 | 16 | BASE_DIR = Path(__file__).parent.joinpath("test_data") 17 | MATCHING_TEST_DATA_PATH = BASE_DIR.joinpath("matching_data.txt") 18 | NON_MATCHING_TEST_DATA_PATH = BASE_DIR.joinpath("non_matching_data.txt") 19 | 20 | 21 | @pytest.fixture 22 | def parser(): 23 | return SimpleConfigParser() 24 | 25 | 26 | @pytest.mark.parametrize("line", load_testdata_from_file(MATCHING_TEST_DATA_PATH)) 27 | def test_match_line_comment(parser, line): 28 | """Test that a line matches the definition of a line comment""" 29 | assert ( 30 | parser._match_empty_line(line) is True 31 | ), f"Expected line '{line}' to match line comment definition!" 32 | 33 | 34 | @pytest.mark.parametrize("line", load_testdata_from_file(NON_MATCHING_TEST_DATA_PATH)) 35 | def test_non_matching_line_comment(parser, line): 36 | """Test that a line does not match the definition of a line comment""" 37 | assert ( 38 | parser._match_empty_line(line) is False 39 | ), f"Expected line '{line}' to not match line comment definition!" 40 | -------------------------------------------------------------------------------- /kiauh/core/submodules/simple_config_parser/tests/line_matching/match_line_comment/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dw-0/kiauh/33113e72e9a45ac1a4020d409b5932e60e4c77eb/kiauh/core/submodules/simple_config_parser/tests/line_matching/match_line_comment/__init__.py -------------------------------------------------------------------------------- /kiauh/core/submodules/simple_config_parser/tests/line_matching/match_line_comment/test_data/matching_data.txt: -------------------------------------------------------------------------------- 1 | ;[example_section] 2 | #[example_section] 3 | # [example_section] 4 | ; [example_section] 5 | ;[gcode_macro CANCEL_PRINT] 6 | #[gcode_macro CANCEL_PRINT] 7 | # [gcode_macro CANCEL_PRINT] 8 | ; [gcode_macro CANCEL_PRINT] 9 | ;[gcode_macro SET_PAUSE_NEXT_LAYER] 10 | #[gcode_macro SET_PAUSE_NEXT_LAYER] 11 | # [gcode_macro SET_PAUSE_NEXT_LAYER] 12 | ; [gcode_macro SET_PAUSE_NEXT_LAYER] 13 | ;[gcode_macro _TOOLHEAD_PARK_PAUSE_CANCEL] 14 | #[gcode_macro _TOOLHEAD_PARK_PAUSE_CANCEL] 15 | # [gcode_macro _TOOLHEAD_PARK_PAUSE_CANCEL] 16 | ; [gcode_macro _TOOLHEAD_PARK_PAUSE_CANCEL] 17 | ;[gcode_macro _TOOLHEAD_PARK_PAUSE_CANCEL] 18 | #[gcode_macro _TOOLHEAD_PARK_PAUSE_CANCEL] 19 | # [gcode_macro _TOOLHEAD_PARK_PAUSE_CANCEL] 20 | ; [gcode_macro _TOOLHEAD_PARK_PAUSE_CANCEL] 21 | ;[gcode_macro _TOOLHEAD_PARK_PAUSE_CANCEL] 22 | #[gcode_macro _TOOLHEAD_PARK_PAUSE_CANCEL] 23 | # [gcode_macro _TOOLHEAD_PARK_PAUSE_CANCEL] 24 | ; [gcode_macro _TOOLHEAD_PARK_PAUSE_CANCEL] 25 | ;[gcode_macro _TOOLHEAD_PARK_PAUSE_CANCEL] 26 | #[gcode_macro _TOOLHEAD_PARK_PAUSE_CANCEL] 27 | # [gcode_macro _TOOLHEAD_PARK_PAUSE_CANCEL] 28 | ; [gcode_macro _TOOLHEAD_PARK_PAUSE_CANCEL] 29 | -------------------------------------------------------------------------------- /kiauh/core/submodules/simple_config_parser/tests/line_matching/match_line_comment/test_data/non_matching_data.txt: -------------------------------------------------------------------------------- 1 | not_a_comment: nono 2 | 3 | [also not a comment] 4 | not_a_comment: ; comment 5 | not_a_comment: # comment 6 | -------------------------------------------------------------------------------- /kiauh/core/submodules/simple_config_parser/tests/line_matching/match_line_comment/test_match_line_comment.py: -------------------------------------------------------------------------------- 1 | # ======================================================================= # 2 | # Copyright (C) 2024 Dominik Willner <th33xitus@gmail.com> # 3 | # # 4 | # https://github.com/dw-0/simple-config-parser # 5 | # # 6 | # This file may be distributed under the terms of the GNU GPLv3 license # 7 | # ======================================================================= # 8 | 9 | from pathlib import Path 10 | 11 | import pytest 12 | 13 | from src.simple_config_parser.simple_config_parser import SimpleConfigParser 14 | from tests.utils import load_testdata_from_file 15 | 16 | BASE_DIR = Path(__file__).parent.joinpath("test_data") 17 | MATCHING_TEST_DATA_PATH = BASE_DIR.joinpath("matching_data.txt") 18 | NON_MATCHING_TEST_DATA_PATH = BASE_DIR.joinpath("non_matching_data.txt") 19 | 20 | 21 | @pytest.fixture 22 | def parser(): 23 | return SimpleConfigParser() 24 | 25 | 26 | @pytest.mark.parametrize("line", load_testdata_from_file(MATCHING_TEST_DATA_PATH)) 27 | def test_match_line_comment(parser, line): 28 | """Test that a line matches the definition of a line comment""" 29 | assert ( 30 | parser._match_line_comment(line) is True 31 | ), f"Expected line '{line}' to match line comment definition!" 32 | 33 | 34 | @pytest.mark.parametrize("line", load_testdata_from_file(NON_MATCHING_TEST_DATA_PATH)) 35 | def test_non_matching_line_comment(parser, line): 36 | """Test that a line does not match the definition of a line comment""" 37 | assert ( 38 | parser._match_line_comment(line) is False 39 | ), f"Expected line '{line}' to not match line comment definition!" 40 | -------------------------------------------------------------------------------- /kiauh/core/submodules/simple_config_parser/tests/line_matching/match_option/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dw-0/kiauh/33113e72e9a45ac1a4020d409b5932e60e4c77eb/kiauh/core/submodules/simple_config_parser/tests/line_matching/match_option/__init__.py -------------------------------------------------------------------------------- /kiauh/core/submodules/simple_config_parser/tests/line_matching/match_option/test_data/non_matching_data.txt: -------------------------------------------------------------------------------- 1 | [section] 2 | [section with spaces] 3 | [section with spaces and comments] ; comment 1 4 | [section with spaces and comments] # comment 2 5 | indented_option: value 6 | option_with_no_value: 7 | another_option_with_no_value: 8 | indented_option_with_no_value: 9 | # position_min: 0 10 | # homing_speed: 5.0 11 | 12 | ### this is a comment 13 | ; this is also a comment 14 | # [section] 15 | # [section with spaces] 16 | # [section with spaces and comments] ; comment 1 17 | ;[section] 18 | ;[section with spaces] 19 | ;[section with spaces and comments] ; comment 1 20 | # commented_option: value 21 | #commented_option: value 22 | ;commented_option: value 23 | ; commented_option: value 24 | # 25 | ; 26 | option_1 :: value 27 | option_1:: value 28 | option_1 ::value 29 | option_2 == value 30 | option_2== value 31 | option_2 ==value 32 | option_1 := value 33 | option_1:= value 34 | option_1 :=value 35 | option_2 := value 36 | option_2:= value 37 | option_2 :=value 38 | -------------------------------------------------------------------------------- /kiauh/core/submodules/simple_config_parser/tests/line_matching/match_option/test_match_option.py: -------------------------------------------------------------------------------- 1 | # ======================================================================= # 2 | # Copyright (C) 2024 Dominik Willner <th33xitus@gmail.com> # 3 | # # 4 | # https://github.com/dw-0/simple-config-parser # 5 | # # 6 | # This file may be distributed under the terms of the GNU GPLv3 license # 7 | # ======================================================================= # 8 | 9 | from pathlib import Path 10 | 11 | import pytest 12 | 13 | from src.simple_config_parser.simple_config_parser import SimpleConfigParser 14 | from tests.utils import load_testdata_from_file 15 | 16 | BASE_DIR = Path(__file__).parent.joinpath("test_data") 17 | MATCHING_TEST_DATA_PATH = BASE_DIR.joinpath("matching_data.txt") 18 | NON_MATCHING_TEST_DATA_PATH = BASE_DIR.joinpath("non_matching_data.txt") 19 | 20 | 21 | @pytest.fixture 22 | def parser(): 23 | return SimpleConfigParser() 24 | 25 | 26 | @pytest.mark.parametrize("line", load_testdata_from_file(MATCHING_TEST_DATA_PATH)) 27 | def test_match_option(parser, line): 28 | """Test that a line matches the definition of an option""" 29 | assert ( 30 | parser._match_option(line) is True 31 | ), f"Expected line '{line}' to match option definition!" 32 | 33 | 34 | @pytest.mark.parametrize("line", load_testdata_from_file(NON_MATCHING_TEST_DATA_PATH)) 35 | def test_non_matching_option(parser, line): 36 | """Test that a line does not match the definition of an option""" 37 | assert ( 38 | parser._match_option(line) is False 39 | ), f"Expected line '{line}' to not match option definition!" 40 | -------------------------------------------------------------------------------- /kiauh/core/submodules/simple_config_parser/tests/line_matching/match_option_block_start/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dw-0/kiauh/33113e72e9a45ac1a4020d409b5932e60e4c77eb/kiauh/core/submodules/simple_config_parser/tests/line_matching/match_option_block_start/__init__.py -------------------------------------------------------------------------------- /kiauh/core/submodules/simple_config_parser/tests/line_matching/match_option_block_start/test_data/matching_data.txt: -------------------------------------------------------------------------------- 1 | trusted_clients: 2 | gcode: 3 | cors_domains: 4 | an_options_block_start_with_comment: ; this is a comment 5 | an_options_block_start_with_comment: # this is a comment 6 | options_block_start_with_comment:;this is a comment 7 | options_block_start_with_comment :;this is a comment 8 | options_block_start_with_comment:#this is a comment 9 | options_block_start_with_comment :#this is a comment 10 | parameter_temperature_(°C): 11 | parameter_temperature_(°C)= 12 | parameter_humidity_(%_RH): 13 | parameter_humidity_(%_RH) : 14 | parameter_spool_weight_(%): 15 | parameter_spool_weight_(%) = 16 | -------------------------------------------------------------------------------- /kiauh/core/submodules/simple_config_parser/tests/line_matching/match_option_block_start/test_data/non_matching_data.txt: -------------------------------------------------------------------------------- 1 | type: jsonfile 2 | path: /dev/shm/drying_box.json 3 | baud: 250000 4 | minimum_cruise_ratio: 0.5 5 | square_corner_velocity: 5.0 6 | full_steps_per_rotation: 200 7 | position_min: 0 8 | homing_speed: 5.0 9 | # baud: 250000 10 | # minimum_cruise_ratio: 0.5 11 | # square_corner_velocity: 5.0 12 | # full_steps_per_rotation: 200 13 | # position_min: 0 14 | # homing_speed: 5.0 15 | 16 | ### this is a comment 17 | ; this is also a comment 18 | ; 19 | # 20 | homing_speed:: 21 | homing_speed:: 22 | homing_speed :: 23 | homing_speed :: 24 | homing_speed== 25 | homing_speed== 26 | homing_speed == 27 | homing_speed == 28 | homing_speed := 29 | homing_speed := 30 | homing_speed =: 31 | homing_speed =: 32 | -------------------------------------------------------------------------------- /kiauh/core/submodules/simple_config_parser/tests/line_matching/match_option_block_start/test_match_options_block_start.py: -------------------------------------------------------------------------------- 1 | # ======================================================================= # 2 | # Copyright (C) 2024 Dominik Willner <th33xitus@gmail.com> # 3 | # # 4 | # https://github.com/dw-0/simple-config-parser # 5 | # # 6 | # This file may be distributed under the terms of the GNU GPLv3 license # 7 | # ======================================================================= # 8 | 9 | from pathlib import Path 10 | 11 | import pytest 12 | 13 | from src.simple_config_parser.simple_config_parser import SimpleConfigParser 14 | from tests.utils import load_testdata_from_file 15 | 16 | BASE_DIR = Path(__file__).parent.joinpath("test_data") 17 | MATCHING_TEST_DATA_PATH = BASE_DIR.joinpath("matching_data.txt") 18 | NON_MATCHING_TEST_DATA_PATH = BASE_DIR.joinpath("non_matching_data.txt") 19 | 20 | 21 | @pytest.fixture 22 | def parser(): 23 | return SimpleConfigParser() 24 | 25 | 26 | @pytest.mark.parametrize("line", load_testdata_from_file(MATCHING_TEST_DATA_PATH)) 27 | def test_match_options_block_start(parser, line): 28 | """Test that a line matches the definition of an options block start""" 29 | assert ( 30 | parser._match_options_block_start(line) is True 31 | ), f"Expected line '{line}' to match options block start definition!" 32 | 33 | 34 | @pytest.mark.parametrize("line", load_testdata_from_file(NON_MATCHING_TEST_DATA_PATH)) 35 | def test_non_matching_options_block_start(parser, line): 36 | """Test that a line does not match the definition of an options block start""" 37 | assert ( 38 | parser._match_options_block_start(line) is False 39 | ), f"Expected line '{line}' to not match options block start definition!" 40 | -------------------------------------------------------------------------------- /kiauh/core/submodules/simple_config_parser/tests/line_matching/match_section/__init__,py.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dw-0/kiauh/33113e72e9a45ac1a4020d409b5932e60e4c77eb/kiauh/core/submodules/simple_config_parser/tests/line_matching/match_section/__init__,py.py -------------------------------------------------------------------------------- /kiauh/core/submodules/simple_config_parser/tests/line_matching/match_section/test_data/matching_data.txt: -------------------------------------------------------------------------------- 1 | [example_section] 2 | [gcode_macro CANCEL_PRINT] 3 | [gcode_macro SET_PAUSE_NEXT_LAYER] 4 | [gcode_macro _TOOLHEAD_PARK_PAUSE_CANCEL] 5 | [update_manager moonraker-obico] 6 | [include moonraker_obico_macros.cfg] 7 | [include moonraker-obico-update.cfg] 8 | [example_section two] 9 | [valid_content] 10 | [valid content] 11 | [content123] 12 | [a] 13 | [valid_content] # comment 14 | [something];comment 15 | [mcu] 16 | [printer] 17 | [printer] 18 | [stepper_x] 19 | [stepper_y] 20 | [stepper_z] 21 | [printer] 22 | [stepper_a] 23 | [stepper_b] 24 | [stepper_c] 25 | [delta_calibrate] 26 | [printer] 27 | [stepper_left] 28 | [stepper_right] 29 | [stepper_bed] 30 | [stepper_arm] 31 | [delta_calibrate] 32 | [extruder] 33 | [heater_bed] 34 | [bed_mesh] 35 | [bed_tilt] 36 | [bed_screws] 37 | [screws_tilt_adjust] 38 | [z_tilt] 39 | [quad_gantry_level] 40 | [skew_correction] 41 | [z_thermal_adjust] 42 | [safe_z_home] 43 | [homing_override] 44 | [endstop_phase stepper_z] 45 | [gcode_macro my_cmd] 46 | [delayed_gcode my_delayed_gcode] 47 | [save_variables] 48 | [idle_timeout] 49 | [virtual_sdcard] 50 | [sdcard_loop] 51 | [force_move] 52 | [pause_resume] 53 | [firmware_retraction] 54 | [gcode_arcs] 55 | [respond] 56 | [exclude_object] 57 | [input_shaper] 58 | [adxl345] 59 | [lis2dw] 60 | [mpu9250 my_accelerometer] 61 | [resonance_tester] 62 | [board_pins my_aliases] 63 | [duplicate_pin_override] 64 | [probe] 65 | [bltouch] 66 | [smart_effector] 67 | [probe_eddy_current my_eddy_probe] 68 | [axis_twist_compensation] 69 | [stepper_z1] 70 | [extruder1] 71 | [dual_carriage] 72 | [extruder_stepper my_extra_stepper] 73 | [manual_stepper my_stepper] 74 | [verify_heater heater_config_name] 75 | [homing_heaters] 76 | [thermistor my_thermistor] 77 | [adc_temperature my_sensor] 78 | [heater_generic my_generic_heater] 79 | [temperature_sensor my_sensor] 80 | [temperature_probe my_probe] 81 | [fan] 82 | [heater_fan heatbreak_cooling_fan] 83 | [controller_fan my_controller_fan] 84 | [temperature_fan my_temp_fan] 85 | [fan_generic extruder_partfan] 86 | [led my_led] 87 | [neopixel my_neopixel] 88 | [dotstar my_dotstar] 89 | [pca9533 my_pca9533] 90 | [pca9632 my_pca9632] 91 | [servo my_servo] 92 | [gcode_button my_gcode_button] 93 | [output_pin my_pin] 94 | [pwm_tool my_tool] 95 | [pwm_cycle_time my_pin] 96 | [static_digital_output my_output_pins] 97 | [multi_pin my_multi_pin] 98 | [tmc2130 stepper_x] 99 | [tmc2208 stepper_x] 100 | [tmc2209 stepper_x] 101 | [tmc2660 stepper_x] 102 | [tmc2240 stepper_x] 103 | [tmc5160 stepper_x] 104 | [ad5206 my_digipot] 105 | [mcp4451 my_digipot] 106 | [mcp4728 my_dac] 107 | [mcp4018 my_digipot] 108 | [display] 109 | [display_data my_group_name my_data_name] 110 | [display_template my_template_name] 111 | [display_glyph my_display_glyph] 112 | [menu __some_list __some_name] 113 | [menu some_name] 114 | [menu some_list] 115 | [menu some_list some_command] 116 | [menu some_list some_input] 117 | [filament_switch_sensor my_sensor] 118 | [filament_motion_sensor my_sensor] 119 | [tsl1401cl_filament_width_sensor] 120 | [hall_filament_width_sensor] 121 | [load_cell] 122 | [sx1509 my_sx1509] 123 | [samd_sercom my_sercom] 124 | [adc_scaled my_name] 125 | [replicape] 126 | [palette2] 127 | [angle my_angle_sensor] 128 | -------------------------------------------------------------------------------- /kiauh/core/submodules/simple_config_parser/tests/line_matching/match_section/test_data/non_matching_data.txt: -------------------------------------------------------------------------------- 1 | section: invalid 2 | not_a_valid_section 3 | [missing_square_bracket 4 | missing_square_bracket] 5 | [] 6 | [ ] 7 | [indented_section] 8 | [indented_section] # comment 9 | [indented_section] ; comment 10 | ;[commented_section] 11 | #[another_commented_section] 12 | ; [commented_section] 13 | # [another_commented_section] 14 | this_is_an_option: 123 15 | this_is_an_indented_option: 123 16 | this_is_an_option_block_start: 17 | 18 | # 19 | ; 20 | -------------------------------------------------------------------------------- /kiauh/core/submodules/simple_config_parser/tests/line_matching/match_section/test_match_section.py: -------------------------------------------------------------------------------- 1 | # ======================================================================= # 2 | # Copyright (C) 2024 Dominik Willner <th33xitus@gmail.com> # 3 | # # 4 | # https://github.com/dw-0/simple-config-parser # 5 | # # 6 | # This file may be distributed under the terms of the GNU GPLv3 license # 7 | # ======================================================================= # 8 | 9 | from pathlib import Path 10 | 11 | import pytest 12 | 13 | from src.simple_config_parser.simple_config_parser import SimpleConfigParser 14 | from tests.utils import load_testdata_from_file 15 | 16 | BASE_DIR = Path(__file__).parent.joinpath("test_data") 17 | MATCHING_TEST_DATA_PATH = BASE_DIR.joinpath("matching_data.txt") 18 | NON_MATCHING_TEST_DATA_PATH = BASE_DIR.joinpath("non_matching_data.txt") 19 | 20 | 21 | @pytest.fixture 22 | def parser(): 23 | return SimpleConfigParser() 24 | 25 | 26 | @pytest.mark.parametrize("line", load_testdata_from_file(MATCHING_TEST_DATA_PATH)) 27 | def test_match_section(parser, line): 28 | """Test that a line matches the definition of a section""" 29 | assert ( 30 | parser._match_section(line) is True 31 | ), f"Expected line '{line}' to match section definition!" 32 | 33 | 34 | @pytest.mark.parametrize("line", load_testdata_from_file(NON_MATCHING_TEST_DATA_PATH)) 35 | def test_non_matching_section(parser, line): 36 | """Test that a line does not match the definition of a section""" 37 | assert ( 38 | parser._match_section(line) is False 39 | ), f"Expected line '{line}' to not match section definition!" 40 | -------------------------------------------------------------------------------- /kiauh/core/submodules/simple_config_parser/tests/line_parsing/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dw-0/kiauh/33113e72e9a45ac1a4020d409b5932e60e4c77eb/kiauh/core/submodules/simple_config_parser/tests/line_parsing/__init__.py -------------------------------------------------------------------------------- /kiauh/core/submodules/simple_config_parser/tests/line_parsing/test_line_parsing.py: -------------------------------------------------------------------------------- 1 | # ======================================================================= # 2 | # Copyright (C) 2024 Dominik Willner <th33xitus@gmail.com> # 3 | # # 4 | # https://github.com/dw-0/simple-config-parser # 5 | # # 6 | # This file may be distributed under the terms of the GNU GPLv3 license # 7 | # ======================================================================= # 8 | import json 9 | from pathlib import Path 10 | 11 | import pytest 12 | 13 | from src.simple_config_parser.constants import HEADER_IDENT, LineType 14 | from src.simple_config_parser.simple_config_parser import SimpleConfigParser 15 | from tests.utils import load_testdata_from_file 16 | 17 | BASE_DIR = Path(__file__).parent.parent.joinpath("assets") 18 | TEST_DATA_PATH = BASE_DIR.joinpath("test_config_1.cfg") 19 | 20 | 21 | @pytest.fixture 22 | def parser(): 23 | parser = SimpleConfigParser() 24 | for line in load_testdata_from_file(TEST_DATA_PATH): 25 | parser._parse_line(line) # noqa 26 | 27 | return parser 28 | 29 | 30 | def test_section_parsing(parser): 31 | expected_keys = {"section_1", "section_2", "section_3", "section_4"} 32 | assert expected_keys.issubset( 33 | parser.config.keys() 34 | ), f"Expected keys: {expected_keys}, got: {parser.config.keys()}" 35 | assert parser.in_option_block is False 36 | assert parser.current_section == parser.get_sections()[-1] 37 | assert parser.config["section_2"] is not None 38 | assert parser.config["section_2"]["header"] == "[section_2] ; comment" 39 | assert parser.config["section_2"]["elements"] is not None 40 | assert len(parser.config["section_2"]["elements"]) > 0 41 | 42 | 43 | def test_option_parsing(parser): 44 | assert parser.config["section_1"]["elements"][0]["type"] == LineType.OPTION.value 45 | assert parser.config["section_1"]["elements"][0]["name"] == "option_1" 46 | assert parser.config["section_1"]["elements"][0]["value"] == "value_1" 47 | assert parser.config["section_1"]["elements"][0]["raw"] == "option_1: value_1" 48 | 49 | 50 | def test_header_parsing(parser): 51 | header = parser.config[HEADER_IDENT] 52 | assert isinstance(header, list) 53 | assert len(header) > 0 54 | 55 | 56 | def test_option_block_parsing(parser): 57 | section = "section number 5" 58 | option_block = None 59 | for element in parser.config[section]["elements"]: 60 | if (element["type"] == LineType.OPTION_BLOCK.value and 61 | element["name"] == "multi_option"): 62 | option_block = element 63 | break 64 | 65 | assert option_block is not None, "multi_option block not found" 66 | assert option_block["type"] == LineType.OPTION_BLOCK.value 67 | assert option_block["name"] == "multi_option" 68 | assert option_block["raw"] == "multi_option:" 69 | 70 | expected_values = [ 71 | "# these are multi-line values", 72 | "value_5_1", 73 | "value_5_2 ; here is a comment", 74 | "value_5_3" 75 | ] 76 | assert option_block["value"] == expected_values, ( 77 | f"Expected values: {expected_values}, " 78 | f"got: {option_block['value']}" 79 | ) 80 | -------------------------------------------------------------------------------- /kiauh/core/submodules/simple_config_parser/tests/public_api/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dw-0/kiauh/33113e72e9a45ac1a4020d409b5932e60e4c77eb/kiauh/core/submodules/simple_config_parser/tests/public_api/__init__.py -------------------------------------------------------------------------------- /kiauh/core/submodules/simple_config_parser/tests/public_api/conftest.py: -------------------------------------------------------------------------------- 1 | # ======================================================================= # 2 | # Copyright (C) 2024 Dominik Willner <th33xitus@gmail.com> # 3 | # # 4 | # https://github.com/dw-0/simple-config-parser # 5 | # # 6 | # This file may be distributed under the terms of the GNU GPLv3 license # 7 | # ======================================================================= # 8 | from pathlib import Path 9 | 10 | import pytest 11 | 12 | from src.simple_config_parser.simple_config_parser import SimpleConfigParser 13 | from tests.utils import load_testdata_from_file 14 | 15 | BASE_DIR = Path(__file__).parent.parent.joinpath("assets") 16 | CONFIG_FILES = ["test_config_1.cfg", "test_config_2.cfg", "test_config_3.cfg"] 17 | 18 | 19 | @pytest.fixture(params=CONFIG_FILES) 20 | def parser(request): 21 | parser = SimpleConfigParser() 22 | file_path = BASE_DIR.joinpath(request.param) 23 | for line in load_testdata_from_file(file_path): 24 | parser._parse_line(line) # noqa 25 | 26 | return parser 27 | -------------------------------------------------------------------------------- /kiauh/core/submodules/simple_config_parser/tests/public_api/test_read_file.py: -------------------------------------------------------------------------------- 1 | # ======================================================================= # 2 | # Copyright (C) 2024 Dominik Willner <th33xitus@gmail.com> # 3 | # # 4 | # https://github.com/dw-0/simple-config-parser # 5 | # # 6 | # This file may be distributed under the terms of the GNU GPLv3 license # 7 | # ======================================================================= # 8 | from pathlib import Path 9 | 10 | from src.simple_config_parser.simple_config_parser import ( 11 | SimpleConfigParser, 12 | ) 13 | 14 | BASE_DIR = Path(__file__).parent.parent.joinpath("assets") 15 | TEST_DATA_PATH = BASE_DIR.joinpath("test_config_1.cfg") 16 | 17 | 18 | def test_read_file(): 19 | parser = SimpleConfigParser() 20 | parser.read_file(TEST_DATA_PATH) 21 | assert parser.config is not None 22 | assert parser.config.keys() is not None 23 | -------------------------------------------------------------------------------- /kiauh/core/submodules/simple_config_parser/tests/public_api/test_sections_api.py: -------------------------------------------------------------------------------- 1 | # ======================================================================= # 2 | # Copyright (C) 2024 Dominik Willner <th33xitus@gmail.com> # 3 | # # 4 | # https://github.com/dw-0/simple-config-parser # 5 | # # 6 | # This file may be distributed under the terms of the GNU GPLv3 license # 7 | # ======================================================================= # 8 | 9 | import pytest 10 | 11 | from src.simple_config_parser.simple_config_parser import ( 12 | DuplicateSectionError, 13 | ) 14 | 15 | 16 | def test_get_sections(parser): 17 | expected_keys = { 18 | "section_1", 19 | "section_2", 20 | "section_3", 21 | "section_4", 22 | "section number 5", 23 | } 24 | assert expected_keys.issubset( 25 | parser.get_sections() 26 | ), f"Expected keys: {expected_keys}, got: {parser.get_sections()}" 27 | 28 | 29 | def test_has_section(parser): 30 | assert parser.has_section("section_1") is True 31 | assert parser.has_section("not_available") is False 32 | 33 | 34 | def test_add_section(parser): 35 | pre_add_count = len(parser.get_sections()) 36 | parser.add_section("new_section") 37 | parser.add_section("new_section2") 38 | assert parser.has_section("new_section") is True 39 | assert parser.has_section("new_section2") is True 40 | assert len(parser.get_sections()) == pre_add_count + 2 41 | 42 | new_section = parser.config["new_section"] 43 | assert isinstance(new_section, dict) 44 | assert new_section["header"] == "[new_section]\n" 45 | assert new_section["elements"] is not None 46 | assert new_section["elements"] == [] 47 | 48 | new_section2 = parser.config["new_section2"] 49 | assert isinstance(new_section2, dict) 50 | assert new_section2["header"] == "[new_section2]\n" 51 | assert new_section2["elements"] is not None 52 | assert new_section2["elements"] == [] 53 | 54 | 55 | def test_add_section_duplicate(parser): 56 | with pytest.raises(DuplicateSectionError): 57 | parser.add_section("section_1") 58 | 59 | 60 | def test_remove_section(parser): 61 | pre_remove_count = len(parser.get_sections()) 62 | parser.remove_section("section_1") 63 | assert parser.has_section("section_1") is False 64 | assert len(parser.get_sections()) == pre_remove_count - 1 65 | assert "section_1" not in parser.config 66 | -------------------------------------------------------------------------------- /kiauh/core/submodules/simple_config_parser/tests/utils.py: -------------------------------------------------------------------------------- 1 | # ======================================================================= # 2 | # Copyright (C) 2024 Dominik Willner <th33xitus@gmail.com> # 3 | # # 4 | # https://github.com/dw-0/simple-config-parser # 5 | # # 6 | # This file may be distributed under the terms of the GNU GPLv3 license # 7 | # ======================================================================= # 8 | from pathlib import Path 9 | 10 | 11 | def load_testdata_from_file(file_path: Path): 12 | """Helper function to load test data from a text file""" 13 | 14 | with open(file_path, "r") as f: 15 | return [line.replace("\n", "") for line in f] 16 | -------------------------------------------------------------------------------- /kiauh/core/submodules/simple_config_parser/tests/value_conversion/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dw-0/kiauh/33113e72e9a45ac1a4020d409b5932e60e4c77eb/kiauh/core/submodules/simple_config_parser/tests/value_conversion/__init__.py -------------------------------------------------------------------------------- /kiauh/core/submodules/simple_config_parser/tests/value_conversion/test_get_conv.py: -------------------------------------------------------------------------------- 1 | # ======================================================================= # 2 | # Copyright (C) 2024 Dominik Willner <th33xitus@gmail.com> # 3 | # # 4 | # https://github.com/dw-0/simple-config-parser # 5 | # # 6 | # This file may be distributed under the terms of the GNU GPLv3 license # 7 | # ======================================================================= # 8 | from pathlib import Path 9 | 10 | import pytest 11 | 12 | from src.simple_config_parser.simple_config_parser import SimpleConfigParser 13 | from tests.utils import load_testdata_from_file 14 | 15 | BASE_DIR = Path(__file__).parent.parent.joinpath("assets") 16 | TEST_DATA_PATH = BASE_DIR.joinpath("test_config_1.cfg") 17 | 18 | 19 | @pytest.fixture 20 | def parser(): 21 | parser = SimpleConfigParser() 22 | for line in load_testdata_from_file(TEST_DATA_PATH): 23 | parser._parse_line(line) # noqa 24 | 25 | return parser 26 | 27 | 28 | def test_get_int_conv(parser): 29 | should_be_int = parser._get_conv("section_1", "option_1_2", int) 30 | assert isinstance(should_be_int, int) 31 | 32 | 33 | def test_get_float_conv(parser): 34 | should_be_float = parser._get_conv("section_1", "option_1_3", float) 35 | assert isinstance(should_be_float, float) 36 | 37 | 38 | def test_get_bool_conv(parser): 39 | should_be_bool = parser._get_conv( 40 | "section_1", "option_1_1", parser._convert_to_boolean 41 | ) 42 | assert isinstance(should_be_bool, bool) 43 | 44 | 45 | def test_get_int_conv_fallback(parser): 46 | should_be_fallback_int = parser._get_conv( 47 | "section_1", "option_128", int, fallback=128 48 | ) 49 | assert isinstance(should_be_fallback_int, int) 50 | assert should_be_fallback_int == 128 51 | assert parser._get_conv("section_1", "option_128", int, None) is None 52 | 53 | 54 | def test_get_float_conv_fallback(parser): 55 | should_be_fallback_float = parser._get_conv( 56 | "section_1", "option_128", float, fallback=1.234 57 | ) 58 | assert isinstance(should_be_fallback_float, float) 59 | assert should_be_fallback_float == 1.234 60 | 61 | assert parser._get_conv("section_1", "option_128", float, None) is None 62 | 63 | 64 | def test_get_bool_conv_fallback(parser): 65 | should_be_fallback_bool = parser._get_conv( 66 | "section_1", "option_128", parser._convert_to_boolean, fallback=True 67 | ) 68 | assert isinstance(should_be_fallback_bool, bool) 69 | assert should_be_fallback_bool is True 70 | 71 | assert ( 72 | parser._get_conv("section_1", "option_128", parser._convert_to_boolean, None) 73 | is None 74 | ) 75 | 76 | 77 | def test_get_int_conv_exception(parser): 78 | with pytest.raises(ValueError): 79 | parser._get_conv("section_1", "option_1", int) 80 | 81 | 82 | def test_get_float_conv_exception(parser): 83 | with pytest.raises(ValueError): 84 | parser._get_conv("section_1", "option_1", float) 85 | 86 | 87 | def test_get_bool_conv_exception(parser): 88 | with pytest.raises(ValueError): 89 | parser._get_conv("section_1", "option_1", parser._convert_to_boolean) 90 | -------------------------------------------------------------------------------- /kiauh/core/types/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dw-0/kiauh/33113e72e9a45ac1a4020d409b5932e60e4c77eb/kiauh/core/types/__init__.py -------------------------------------------------------------------------------- /kiauh/core/types/color.py: -------------------------------------------------------------------------------- 1 | # ======================================================================= # 2 | # Copyright (C) 2020 - 2025 Dominik Willner <th33xitus@gmail.com> # 3 | # # 4 | # This file is part of KIAUH - Klipper Installation And Update Helper # 5 | # https://github.com/dw-0/kiauh # 6 | # # 7 | # This file may be distributed under the terms of the GNU GPLv3 license # 8 | # ======================================================================= # 9 | from __future__ import annotations 10 | 11 | from enum import Enum 12 | 13 | 14 | class Color(Enum): 15 | WHITE = "\033[37m" # white 16 | MAGENTA = "\033[35m" # magenta 17 | GREEN = "\033[92m" # bright green 18 | YELLOW = "\033[93m" # bright yellow 19 | RED = "\033[91m" # bright red 20 | CYAN = "\033[96m" # bright cyan 21 | RST = "\033[0m" # reset format 22 | 23 | def __str__(self): 24 | return self.value 25 | 26 | @staticmethod 27 | def apply(text: str | int, color: "Color") -> str: 28 | """Apply a given color to a given text string.""" 29 | return f"{color}{text}{Color.RST}" 30 | -------------------------------------------------------------------------------- /kiauh/core/types/component_status.py: -------------------------------------------------------------------------------- 1 | # ======================================================================= # 2 | # Copyright (C) 2020 - 2025 Dominik Willner <th33xitus@gmail.com> # 3 | # # 4 | # This file is part of KIAUH - Klipper Installation And Update Helper # 5 | # https://github.com/dw-0/kiauh # 6 | # # 7 | # This file may be distributed under the terms of the GNU GPLv3 license # 8 | # ======================================================================= # 9 | from __future__ import annotations 10 | 11 | from dataclasses import dataclass 12 | from typing import Dict, Literal 13 | 14 | StatusText = Literal["Installed", "Not installed", "Incomplete"] 15 | StatusCode = Literal[0, 1, 2] 16 | StatusMap: Dict[StatusCode, StatusText] = { 17 | 0: "Not installed", 18 | 1: "Incomplete", 19 | 2: "Installed", 20 | } 21 | 22 | 23 | @dataclass 24 | class ComponentStatus: 25 | status: StatusCode 26 | owner: str | None = None 27 | repo: str | None = None 28 | repo_url: str | None = None 29 | branch: str = "" 30 | local: str | None = None 31 | remote: str | None = None 32 | instances: int | None = None 33 | -------------------------------------------------------------------------------- /kiauh/extensions/__init__.py: -------------------------------------------------------------------------------- 1 | # ======================================================================= # 2 | # Copyright (C) 2020 - 2025 Dominik Willner <th33xitus@gmail.com> # 3 | # # 4 | # This file is part of KIAUH - Klipper Installation And Update Helper # 5 | # https://github.com/dw-0/kiauh # 6 | # # 7 | # This file may be distributed under the terms of the GNU GPLv3 license # 8 | # ======================================================================= # 9 | 10 | from pathlib import Path 11 | 12 | EXTENSION_ROOT = Path(__file__).resolve().parents[1].joinpath("extensions") 13 | -------------------------------------------------------------------------------- /kiauh/extensions/base_extension.py: -------------------------------------------------------------------------------- 1 | # ======================================================================= # 2 | # Copyright (C) 2020 - 2025 Dominik Willner <th33xitus@gmail.com> # 3 | # # 4 | # This file is part of KIAUH - Klipper Installation And Update Helper # 5 | # https://github.com/dw-0/kiauh # 6 | # # 7 | # This file may be distributed under the terms of the GNU GPLv3 license # 8 | # ======================================================================= # 9 | 10 | from abc import ABC, abstractmethod 11 | from typing import Dict 12 | 13 | 14 | # noinspection PyUnusedLocal 15 | # noinspection PyMethodMayBeStatic 16 | class BaseExtension(ABC): 17 | def __init__(self, metadata: Dict[str, str]): 18 | self.metadata = metadata 19 | 20 | @abstractmethod 21 | def install_extension(self, **kwargs) -> None: 22 | raise NotImplementedError 23 | 24 | def update_extension(self, **kwargs) -> None: 25 | raise NotImplementedError 26 | 27 | @abstractmethod 28 | def remove_extension(self, **kwargs) -> None: 29 | raise NotImplementedError 30 | -------------------------------------------------------------------------------- /kiauh/extensions/gcode_shell_cmd/__init__.py: -------------------------------------------------------------------------------- 1 | # ======================================================================= # 2 | # Copyright (C) 2020 - 2025 Dominik Willner <th33xitus@gmail.com> # 3 | # # 4 | # This file is part of KIAUH - Klipper Installation And Update Helper # 5 | # https://github.com/dw-0/kiauh # 6 | # # 7 | # This file may be distributed under the terms of the GNU GPLv3 license # 8 | # ======================================================================= # 9 | 10 | from pathlib import Path 11 | 12 | EXT_MODULE_NAME = "gcode_shell_command.py" 13 | MODULE_PATH = Path(__file__).resolve().parent 14 | MODULE_ASSETS = MODULE_PATH.joinpath("assets") 15 | KLIPPER_DIR = Path.home().joinpath("klipper") 16 | KLIPPER_EXTRAS = KLIPPER_DIR.joinpath("klippy/extras") 17 | EXTENSION_SRC = MODULE_ASSETS.joinpath(EXT_MODULE_NAME) 18 | EXTENSION_TARGET_PATH = KLIPPER_EXTRAS.joinpath(EXT_MODULE_NAME) 19 | EXAMPLE_CFG_SRC = MODULE_ASSETS.joinpath("shell_command.cfg") 20 | -------------------------------------------------------------------------------- /kiauh/extensions/gcode_shell_cmd/assets/gcode_shell_command.py: -------------------------------------------------------------------------------- 1 | # Run a shell command via gcode 2 | # 3 | # Copyright (C) 2019 Eric Callahan <arksine.code@gmail.com> 4 | # 5 | # This file may be distributed under the terms of the GNU GPLv3 license. 6 | import logging 7 | import os 8 | import shlex 9 | import subprocess 10 | 11 | 12 | class ShellCommand: 13 | def __init__(self, config): 14 | self.name = config.get_name().split()[-1] 15 | self.printer = config.get_printer() 16 | self.gcode = self.printer.lookup_object("gcode") 17 | cmd = config.get("command") 18 | cmd = os.path.expanduser(cmd) 19 | self.command = shlex.split(cmd) 20 | self.timeout = config.getfloat("timeout", 2.0, above=0.0) 21 | self.verbose = config.getboolean("verbose", True) 22 | self.proc_fd = None 23 | self.partial_output = "" 24 | self.gcode.register_mux_command( 25 | "RUN_SHELL_COMMAND", 26 | "CMD", 27 | self.name, 28 | self.cmd_RUN_SHELL_COMMAND, 29 | desc=self.cmd_RUN_SHELL_COMMAND_help, 30 | ) 31 | 32 | def _process_output(self, eventime): 33 | if self.proc_fd is None: 34 | return 35 | try: 36 | data = os.read(self.proc_fd, 4096) 37 | except Exception: 38 | pass 39 | data = self.partial_output + data.decode() 40 | if "\n" not in data: 41 | self.partial_output = data 42 | return 43 | elif data[-1] != "\n": 44 | split = data.rfind("\n") + 1 45 | self.partial_output = data[split:] 46 | data = data[:split] 47 | else: 48 | self.partial_output = "" 49 | self.gcode.respond_info(data) 50 | 51 | cmd_RUN_SHELL_COMMAND_help = "Run a linux shell command" 52 | 53 | def cmd_RUN_SHELL_COMMAND(self, params): 54 | gcode_params = params.get("PARAMS", "") 55 | gcode_params = shlex.split(gcode_params) 56 | reactor = self.printer.get_reactor() 57 | try: 58 | proc = subprocess.Popen( 59 | self.command + gcode_params, 60 | stdout=subprocess.PIPE, 61 | stderr=subprocess.STDOUT, 62 | ) 63 | except Exception: 64 | logging.exception("shell_command: Command {%s} failed" % (self.name)) 65 | raise self.gcode.error("Error running command {%s}" % (self.name)) 66 | if self.verbose: 67 | self.proc_fd = proc.stdout.fileno() 68 | self.gcode.respond_info("Running Command {%s}...:" % (self.name)) 69 | hdl = reactor.register_fd(self.proc_fd, self._process_output) 70 | eventtime = reactor.monotonic() 71 | endtime = eventtime + self.timeout 72 | complete = False 73 | while eventtime < endtime: 74 | eventtime = reactor.pause(eventtime + 0.05) 75 | if proc.poll() is not None: 76 | complete = True 77 | break 78 | if not complete: 79 | proc.terminate() 80 | if self.verbose: 81 | if self.partial_output: 82 | self.gcode.respond_info(self.partial_output) 83 | self.partial_output = "" 84 | if complete: 85 | msg = "Command {%s} finished\n" % (self.name) 86 | else: 87 | msg = "Command {%s} timed out" % (self.name) 88 | self.gcode.respond_info(msg) 89 | reactor.unregister_fd(hdl) 90 | self.proc_fd = None 91 | 92 | 93 | def load_config_prefix(config): 94 | return ShellCommand(config) 95 | -------------------------------------------------------------------------------- /kiauh/extensions/gcode_shell_cmd/assets/shell_command.cfg: -------------------------------------------------------------------------------- 1 | [gcode_shell_command hello_world] 2 | command: echo hello world 3 | timeout: 2. 4 | verbose: True 5 | [gcode_macro HELLO_WORLD] 6 | gcode: 7 | RUN_SHELL_COMMAND CMD=hello_world -------------------------------------------------------------------------------- /kiauh/extensions/gcode_shell_cmd/metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "metadata": { 3 | "index": 1, 4 | "module": "gcode_shell_cmd_extension", 5 | "maintained_by": "dw-0", 6 | "display_name": "G-Code Shell Command", 7 | "description": ["Run a shell commands from gcode."] 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /kiauh/extensions/klipper_backup/__init__.py: -------------------------------------------------------------------------------- 1 | # ======================================================================= # 2 | # Copyright (C) 2023 - 2024 Staubgeborener and Tylerjet # 3 | # https://github.com/Staubgeborener/klipper-backup # 4 | # https://klipperbackup.xyz # 5 | # # 6 | # This file is part of KIAUH - Klipper Installation And Update Helper # 7 | # https://github.com/dw-0/kiauh # 8 | # # 9 | # This file may be distributed under the terms of the GNU GPLv3 license # 10 | # ======================================================================= # 11 | 12 | from pathlib import Path 13 | 14 | EXT_MODULE_NAME = "klipper_backup_extension.py" 15 | MODULE_PATH = Path(__file__).resolve().parent 16 | MOONRAKER_CONF = Path.home().joinpath("printer_data", "config", "moonraker.conf") 17 | KLIPPERBACKUP_DIR = Path.home().joinpath("klipper-backup") 18 | KLIPPERBACKUP_CONFIG_DIR = Path.home().joinpath("config_backup") 19 | KLIPPERBACKUP_REPO_URL = "https://github.com/staubgeborener/klipper-backup" 20 | -------------------------------------------------------------------------------- /kiauh/extensions/klipper_backup/metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "metadata": { 3 | "index": 4, 4 | "module": "klipper_backup_extension", 5 | "maintained_by": "Staubgeborener", 6 | "display_name": "Klipper-Backup", 7 | "description": ["Backup all your Klipper files to GitHub"], 8 | "updates": true 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /kiauh/extensions/mainsail_theme_installer/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dw-0/kiauh/33113e72e9a45ac1a4020d409b5932e60e4c77eb/kiauh/extensions/mainsail_theme_installer/__init__.py -------------------------------------------------------------------------------- /kiauh/extensions/mainsail_theme_installer/metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "metadata": { 3 | "index": 2, 4 | "module": "mainsail_theme_installer_extension", 5 | "maintained_by": "dw-0", 6 | "display_name": "Mainsail Theme Installer", 7 | "description": ["Install Mainsail Themes maintained by the Mainsail community."] 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /kiauh/extensions/mobileraker/__init__.py: -------------------------------------------------------------------------------- 1 | # ======================================================================= # 2 | # Copyright (C) 2020 - 2025 Dominik Willner <th33xitus@gmail.com> # 3 | # # 4 | # This file is part of KIAUH - Klipper Installation And Update Helper # 5 | # https://github.com/dw-0/kiauh # 6 | # # 7 | # This file may be distributed under the terms of the GNU GPLv3 license # 8 | # ======================================================================= # 9 | 10 | from pathlib import Path 11 | 12 | from core.backup_manager import BACKUP_ROOT_DIR 13 | from core.constants import SYSTEMD 14 | 15 | # repo 16 | MOBILERAKER_REPO = "https://github.com/Clon1998/mobileraker_companion.git" 17 | 18 | # names 19 | MOBILERAKER_SERVICE_NAME = "mobileraker.service" 20 | MOBILERAKER_UPDATER_SECTION_NAME = "update_manager mobileraker" 21 | MOBILERAKER_LOG_NAME = "mobileraker.log" 22 | 23 | # directories 24 | MOBILERAKER_DIR = Path.home().joinpath("mobileraker_companion") 25 | MOBILERAKER_ENV_DIR = Path.home().joinpath("mobileraker-env") 26 | MOBILERAKER_BACKUP_DIR = BACKUP_ROOT_DIR.joinpath("mobileraker-backups") 27 | 28 | # files 29 | MOBILERAKER_INSTALL_SCRIPT = MOBILERAKER_DIR.joinpath("scripts/install.sh") 30 | MOBILERAKER_REQ_FILE = MOBILERAKER_DIR.joinpath("scripts/mobileraker-requirements.txt") 31 | MOBILERAKER_SERVICE_FILE = SYSTEMD.joinpath(MOBILERAKER_SERVICE_NAME) 32 | -------------------------------------------------------------------------------- /kiauh/extensions/mobileraker/metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "metadata": { 3 | "index": 3, 4 | "module": "mobileraker_extension", 5 | "maintained_by": "Clon1998", 6 | "display_name": "Mobileraker", 7 | "description": [ 8 | "Companion for Mobileraker, enabling push notification for Klipper using Moonraker." 9 | ], 10 | "updates": true 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /kiauh/extensions/obico/__init__.py: -------------------------------------------------------------------------------- 1 | # ======================================================================= # 2 | # Copyright (C) 2020 - 2025 Dominik Willner <th33xitus@gmail.com> # 3 | # # 4 | # This file is part of KIAUH - Klipper Installation And Update Helper # 5 | # https://github.com/dw-0/kiauh # 6 | # # 7 | # This file may be distributed under the terms of the GNU GPLv3 license # 8 | # ======================================================================= # 9 | from pathlib import Path 10 | 11 | MODULE_PATH = Path(__file__).resolve().parent 12 | 13 | # repo 14 | OBICO_REPO = "https://github.com/TheSpaghettiDetective/moonraker-obico.git" 15 | 16 | # names 17 | OBICO_SERVICE_NAME = "moonraker-obico.service" 18 | OBICO_ENV_FILE_NAME = "moonraker-obico.env" 19 | OBICO_CFG_NAME = "moonraker-obico.cfg" 20 | OBICO_CFG_SAMPLE_NAME = "moonraker-obico.cfg.sample" 21 | OBICO_LOG_NAME = "moonraker-obico.log" 22 | OBICO_UPDATE_CFG_NAME = "moonraker-obico-update.cfg" 23 | OBICO_UPDATE_CFG_SAMPLE_NAME = "moonraker-obico-update.cfg.sample" 24 | OBICO_MACROS_CFG_NAME = "moonraker_obico_macros.cfg" 25 | 26 | # directories 27 | OBICO_DIR = Path.home().joinpath("moonraker-obico") 28 | OBICO_ENV_DIR = Path.home().joinpath("moonraker-obico-env") 29 | 30 | # files 31 | OBICO_SERVICE_TEMPLATE = MODULE_PATH.joinpath(f"assets/{OBICO_SERVICE_NAME}") 32 | OBICO_ENV_FILE_TEMPLATE = MODULE_PATH.joinpath(f"assets/{OBICO_ENV_FILE_NAME}") 33 | OBICO_LINK_SCRIPT = OBICO_DIR.joinpath("scripts/link.sh") 34 | OBICO_REQ_FILE = OBICO_DIR.joinpath("requirements.txt") 35 | -------------------------------------------------------------------------------- /kiauh/extensions/obico/assets/moonraker-obico.env: -------------------------------------------------------------------------------- 1 | OBICO_ARGS="-m moonraker_obico.app -c %CFG%" 2 | -------------------------------------------------------------------------------- /kiauh/extensions/obico/assets/moonraker-obico.service: -------------------------------------------------------------------------------- 1 | #Systemd service file for moonraker-obico 2 | [Unit] 3 | Description=Moonraker-Obico 4 | After=network-online.target moonraker.service 5 | 6 | [Install] 7 | WantedBy=multi-user.target 8 | 9 | [Service] 10 | Type=simple 11 | User=%USER% 12 | WorkingDirectory=%OBICO_DIR% 13 | EnvironmentFile=%ENV_FILE% 14 | ExecStart=%ENV%/bin/python3 $OBICO_ARGS 15 | Restart=always 16 | RestartSec=5 17 | -------------------------------------------------------------------------------- /kiauh/extensions/obico/metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "metadata": { 3 | "index": 6, 4 | "module": "moonraker_obico_extension", 5 | "maintained_by": "Obico", 6 | "display_name": "Obico for Klipper", 7 | "description": [ 8 | "Open source 3D Printing cloud and AI", 9 | "- AI-Powered Failure Detection", 10 | "- Free Remote Monitoring and Access", 11 | "- 25FPS High-Def Webcam Streaming", 12 | "- Free 4.9-Star Mobile App" 13 | ], 14 | "updates": true 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /kiauh/extensions/octoapp/__init__.py: -------------------------------------------------------------------------------- 1 | # ======================================================================= # 2 | # Copyright (C) 2020 - 2025 Dominik Willner <th33xitus@gmail.com> # 3 | # # 4 | # This file is part of KIAUH - Klipper Installation And Update Helper # 5 | # https://github.com/dw-0/kiauh # 6 | # # 7 | # This file may be distributed under the terms of the GNU GPLv3 license # 8 | # ======================================================================= # 9 | from pathlib import Path 10 | 11 | # repo 12 | OA_REPO = "https://github.com/crysxd/OctoApp-Plugin.git" 13 | 14 | # directories 15 | OA_DIR = Path.home().joinpath("octoapp") 16 | OA_ENV_DIR = Path.home().joinpath("octoapp-env") 17 | 18 | # files 19 | OA_REQ_FILE = OA_DIR.joinpath("requirements.txt") 20 | OA_DEPS_JSON_FILE = OA_DIR.joinpath("moonraker-system-dependencies.json") 21 | OA_INSTALL_SCRIPT = OA_DIR.joinpath("install.sh") 22 | OA_UPDATE_SCRIPT = OA_DIR.joinpath("update.sh") 23 | OA_INSTALLER_LOG_FILE = Path.home().joinpath("octoapp-installer.log") 24 | 25 | # filenames 26 | OA_CFG_NAME = "octoapp.conf" 27 | OA_LOG_NAME = "octoapp.log" 28 | OA_SYS_CFG_NAME = "octoapp-system.cfg" 29 | -------------------------------------------------------------------------------- /kiauh/extensions/octoapp/metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "metadata": { 3 | "index": 9, 4 | "module": "octoapp_extension", 5 | "maintained_by": "crysxd", 6 | "display_name": "OctoApp for Klipper", 7 | "description": [ 8 | "Your favorite 3D printing app for iOS & Android", 9 | "- Print notifications on your phone & watch", 10 | "- Control and start prints from your phone", 11 | "- Live webcam view", 12 | "- Live Gcode preview", 13 | "- And much much more!" 14 | ], 15 | "updates": true 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /kiauh/extensions/octoapp/octoapp.py: -------------------------------------------------------------------------------- 1 | # ======================================================================= # 2 | # Copyright (C) 2020 - 2025 Dominik Willner <th33xitus@gmail.com> # 3 | # # 4 | # This file is part of KIAUH - Klipper Installation And Update Helper # 5 | # https://github.com/dw-0/kiauh # 6 | # # 7 | # This file may be distributed under the terms of the GNU GPLv3 license # 8 | # ======================================================================= # 9 | from __future__ import annotations 10 | 11 | from dataclasses import dataclass, field 12 | from pathlib import Path 13 | from subprocess import CalledProcessError, run 14 | 15 | from components.moonraker import MOONRAKER_CFG_NAME 16 | from components.moonraker.moonraker import Moonraker 17 | from core.instance_manager.base_instance import BaseInstance 18 | from core.logger import Logger 19 | from extensions.octoapp import ( 20 | OA_CFG_NAME, 21 | OA_DIR, 22 | OA_ENV_DIR, 23 | OA_INSTALL_SCRIPT, 24 | OA_LOG_NAME, 25 | OA_SYS_CFG_NAME, 26 | OA_UPDATE_SCRIPT, 27 | ) 28 | from utils.sys_utils import get_service_file_path 29 | 30 | 31 | @dataclass 32 | class Octoapp: 33 | suffix: str 34 | base: BaseInstance = field(init=False, repr=False) 35 | service_file_path: Path = field(init=False) 36 | log_file_name = OA_LOG_NAME 37 | dir: Path = OA_DIR 38 | env_dir: Path = OA_ENV_DIR 39 | data_dir: Path = field(init=False) 40 | store_dir: Path = field(init=False) 41 | cfg_file: Path = field(init=False) 42 | sys_cfg_file: Path = field(init=False) 43 | 44 | def __post_init__(self): 45 | self.base: BaseInstance = BaseInstance(Moonraker, self.suffix) 46 | self.base.log_file_name = self.log_file_name 47 | 48 | self.service_file_path: Path = get_service_file_path(Octoapp, self.suffix) 49 | self.store_dir = self.base.data_dir.joinpath("store") 50 | self.cfg_file = self.base.cfg_dir.joinpath(OA_CFG_NAME) 51 | self.sys_cfg_file = self.base.cfg_dir.joinpath(OA_SYS_CFG_NAME) 52 | self.data_dir = self.base.data_dir 53 | self.sys_cfg_file = self.base.cfg_dir.joinpath(OA_SYS_CFG_NAME) 54 | 55 | def create(self) -> None: 56 | Logger.print_status("Creating OctoApp for Klipper Instance ...") 57 | 58 | try: 59 | cmd = f"{OA_INSTALL_SCRIPT} {self.base.cfg_dir}/{MOONRAKER_CFG_NAME}" 60 | run(cmd, check=True, shell=True) 61 | 62 | except CalledProcessError as e: 63 | Logger.print_error(f"Error creating instance: {e}") 64 | raise 65 | 66 | @staticmethod 67 | def update() -> None: 68 | try: 69 | run(OA_UPDATE_SCRIPT.as_posix(), check=True, shell=True, cwd=OA_DIR) 70 | 71 | except CalledProcessError as e: 72 | Logger.print_error(f"Error updating OctoApp for Klipper: {e}") 73 | raise 74 | -------------------------------------------------------------------------------- /kiauh/extensions/octoeverywhere/__init__.py: -------------------------------------------------------------------------------- 1 | # ======================================================================= # 2 | # Copyright (C) 2020 - 2025 Dominik Willner <th33xitus@gmail.com> # 3 | # # 4 | # This file is part of KIAUH - Klipper Installation And Update Helper # 5 | # https://github.com/dw-0/kiauh # 6 | # # 7 | # This file may be distributed under the terms of the GNU GPLv3 license # 8 | # ======================================================================= # 9 | from pathlib import Path 10 | 11 | # repo 12 | OE_REPO = "https://github.com/QuinnDamerell/OctoPrint-OctoEverywhere.git" 13 | 14 | # directories 15 | OE_DIR = Path.home().joinpath("octoeverywhere") 16 | OE_ENV_DIR = Path.home().joinpath("octoeverywhere-env") 17 | OE_STORE_DIR = OE_DIR.joinpath("octoeverywhere-store") 18 | 19 | # files 20 | OE_REQ_FILE = OE_DIR.joinpath("requirements.txt") 21 | OE_DEPS_JSON_FILE = OE_DIR.joinpath("moonraker-system-dependencies.json") 22 | OE_INSTALL_SCRIPT = OE_DIR.joinpath("install.sh") 23 | OE_UPDATE_SCRIPT = OE_DIR.joinpath("update.sh") 24 | OE_INSTALLER_LOG_FILE = Path.home().joinpath("octoeverywhere-installer.log") 25 | 26 | # filenames 27 | OE_CFG_NAME = "octoeverywhere.conf" 28 | OE_LOG_NAME = "octoeverywhere.log" 29 | OE_SYS_CFG_NAME = "octoeverywhere-system.cfg" 30 | -------------------------------------------------------------------------------- /kiauh/extensions/octoeverywhere/metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "metadata": { 3 | "index": 7, 4 | "module": "octoeverywhere_extension", 5 | "maintained_by": "QuinnDamerell", 6 | "display_name": "OctoEverywhere for Klipper", 7 | "description": [ 8 | "Cloud Empower Your Klipper 3D Printers With:", 9 | "- Free, Private, And Secure Remote Access", 10 | "- AI Print Failure Detection", 11 | "- Real-time Notifications", 12 | "- Live Streaming, and More!" 13 | ], 14 | "updates": true 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /kiauh/extensions/octoeverywhere/octoeverywhere.py: -------------------------------------------------------------------------------- 1 | # ======================================================================= # 2 | # Copyright (C) 2020 - 2025 Dominik Willner <th33xitus@gmail.com> # 3 | # # 4 | # This file is part of KIAUH - Klipper Installation And Update Helper # 5 | # https://github.com/dw-0/kiauh # 6 | # # 7 | # This file may be distributed under the terms of the GNU GPLv3 license # 8 | # ======================================================================= # 9 | from __future__ import annotations 10 | 11 | from dataclasses import dataclass, field 12 | from pathlib import Path 13 | from subprocess import CalledProcessError, run 14 | 15 | from components.moonraker import MOONRAKER_CFG_NAME 16 | from components.moonraker.moonraker import Moonraker 17 | from core.instance_manager.base_instance import BaseInstance 18 | from core.logger import Logger 19 | from extensions.octoeverywhere import ( 20 | OE_CFG_NAME, 21 | OE_DIR, 22 | OE_ENV_DIR, 23 | OE_INSTALL_SCRIPT, 24 | OE_LOG_NAME, 25 | OE_SYS_CFG_NAME, 26 | OE_UPDATE_SCRIPT, 27 | ) 28 | from utils.sys_utils import get_service_file_path 29 | 30 | 31 | @dataclass 32 | class Octoeverywhere: 33 | suffix: str 34 | base: BaseInstance = field(init=False, repr=False) 35 | service_file_path: Path = field(init=False) 36 | log_file_name = OE_LOG_NAME 37 | dir: Path = OE_DIR 38 | env_dir: Path = OE_ENV_DIR 39 | data_dir: Path = field(init=False) 40 | store_dir: Path = field(init=False) 41 | cfg_file: Path = field(init=False) 42 | sys_cfg_file: Path = field(init=False) 43 | 44 | def __post_init__(self): 45 | self.base: BaseInstance = BaseInstance(Moonraker, self.suffix) 46 | self.base.log_file_name = self.log_file_name 47 | 48 | self.service_file_path: Path = get_service_file_path( 49 | Octoeverywhere, self.suffix 50 | ) 51 | self.store_dir = self.base.data_dir.joinpath("store") 52 | self.cfg_file = self.base.cfg_dir.joinpath(OE_CFG_NAME) 53 | self.sys_cfg_file = self.base.cfg_dir.joinpath(OE_SYS_CFG_NAME) 54 | self.data_dir = self.base.data_dir 55 | self.sys_cfg_file = self.base.cfg_dir.joinpath(OE_SYS_CFG_NAME) 56 | 57 | def create(self) -> None: 58 | Logger.print_status("Creating OctoEverywhere for Klipper Instance ...") 59 | 60 | try: 61 | cmd = f"{OE_INSTALL_SCRIPT} {self.base.cfg_dir}/{MOONRAKER_CFG_NAME}" 62 | run(cmd, check=True, shell=True) 63 | 64 | except CalledProcessError as e: 65 | Logger.print_error(f"Error creating instance: {e}") 66 | raise 67 | 68 | @staticmethod 69 | def update() -> None: 70 | try: 71 | run(OE_UPDATE_SCRIPT.as_posix(), check=True, shell=True, cwd=OE_DIR) 72 | 73 | except CalledProcessError as e: 74 | Logger.print_error(f"Error updating OctoEverywhere for Klipper: {e}") 75 | raise 76 | -------------------------------------------------------------------------------- /kiauh/extensions/pretty_gcode/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dw-0/kiauh/33113e72e9a45ac1a4020d409b5932e60e4c77eb/kiauh/extensions/pretty_gcode/__init__.py -------------------------------------------------------------------------------- /kiauh/extensions/pretty_gcode/assets/pgcode.local.conf: -------------------------------------------------------------------------------- 1 | # PrettyGCode website configuration 2 | # copy this file to /etc/nginx/sites-available/pgcode.local.conf 3 | # then to enable: 4 | # sudo ln -s /etc/nginx/sites-available/pgcode.local.conf /etc/nginx/sites-enabled/pgcode.local.conf 5 | # then restart ngninx: 6 | # sudo systemctl reload nginx 7 | server { 8 | listen %PORT%; 9 | listen [::]:%PORT%; 10 | server_name pgcode.local; 11 | 12 | root %ROOT_DIR%; 13 | 14 | index pgcode.html; 15 | 16 | location / { 17 | try_files $uri $uri/ =404; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /kiauh/extensions/pretty_gcode/metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "metadata": { 3 | "index": 8, 4 | "module": "pretty_gcode_extension", 5 | "maintained_by": "Kragrathea", 6 | "display_name": "PrettyGCode for Klipper", 7 | "description": ["3D G-Code viewer for Klipper"], 8 | "updates": true 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /kiauh/extensions/simply_print/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dw-0/kiauh/33113e72e9a45ac1a4020d409b5932e60e4c77eb/kiauh/extensions/simply_print/__init__.py -------------------------------------------------------------------------------- /kiauh/extensions/simply_print/metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "metadata": { 3 | "index": 10, 4 | "module": "simply_print_extension", 5 | "maintained_by": "dw-0", 6 | "display_name": "SimplyPrint", 7 | "description": [ 8 | "3D Printer Cloud Management Software.", 9 | "\n\n", 10 | "3D printing doesn't have to be a complicated, analog, SD card-filled experience; step into the future of modern 3D printing" 11 | ] 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /kiauh/extensions/spoolman/__init__.py: -------------------------------------------------------------------------------- 1 | # ======================================================================= # 2 | # Copyright (C) 2020 - 2025 Dominik Willner <th33xitus@gmail.com> # 3 | # # 4 | # This file is part of KIAUH - Klipper Installation And Update Helper # 5 | # https://github.com/dw-0/kiauh # 6 | # # 7 | # This file may be distributed under the terms of the GNU GPLv3 license # 8 | # ======================================================================= # 9 | from pathlib import Path 10 | 11 | MODULE_PATH = Path(__file__).resolve().parent 12 | SPOOLMAN_DOCKER_IMAGE = "ghcr.io/donkie/spoolman:latest" 13 | SPOOLMAN_DIR = Path.home().joinpath("spoolman") 14 | SPOOLMAN_DATA_DIR = SPOOLMAN_DIR.joinpath("data") 15 | SPOOLMAN_COMPOSE_FILE = SPOOLMAN_DIR.joinpath("docker-compose.yml") 16 | SPOOLMAN_DEFAULT_PORT = 7912 17 | -------------------------------------------------------------------------------- /kiauh/extensions/spoolman/assets/docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | spoolman: 3 | image: ghcr.io/donkie/spoolman:latest 4 | restart: unless-stopped 5 | volumes: 6 | # Mount the host machine's ./data directory into the container's /home/app/.local/share/spoolman directory 7 | - type: bind 8 | source: ./data # This is where the data will be stored locally. Could also be set to for example `source: /home/pi/printer_data/spoolman`. 9 | target: /home/app/.local/share/spoolman # Do NOT modify this line 10 | ports: 11 | # Map the host machine's port 7912 to the container's port 8000 12 | - "7912:8000" 13 | environment: 14 | - TZ=Europe/Stockholm # Optional, defaults to UTC 15 | -------------------------------------------------------------------------------- /kiauh/extensions/spoolman/metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "metadata": { 3 | "index": 11, 4 | "module": "spoolman_extension", 5 | "maintained_by": "dw-0", 6 | "display_name": "Spoolman (Docker)", 7 | "description": [ 8 | "Filament manager for 3D printing", 9 | "- Track your filament inventory", 10 | "- Monitor filament usage", 11 | "- Manage vendors, materials, and spools", 12 | "- Integrates with Moonraker", 13 | "\n\n", 14 | "Note: This extension installs Spoolman using Docker. Docker must be installed on your system before installing Spoolman." 15 | ], 16 | "updates": true 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /kiauh/extensions/telegram_bot/__init__.py: -------------------------------------------------------------------------------- 1 | # ======================================================================= # 2 | # Copyright (C) 2020 - 2025 Dominik Willner <th33xitus@gmail.com> # 3 | # # 4 | # This file is part of KIAUH - Klipper Installation And Update Helper # 5 | # https://github.com/dw-0/kiauh # 6 | # # 7 | # This file may be distributed under the terms of the GNU GPLv3 license # 8 | # ======================================================================= # 9 | from pathlib import Path 10 | 11 | MODULE_PATH = Path(__file__).resolve().parent 12 | 13 | # repo 14 | TG_BOT_REPO = "https://github.com/nlef/moonraker-telegram-bot.git" 15 | 16 | # names 17 | TG_BOT_CFG_NAME = "telegram.conf" 18 | TG_BOT_LOG_NAME = "telegram.log" 19 | TG_BOT_SERVICE_NAME = "moonraker-telegram-bot.service" 20 | TG_BOT_ENV_FILE_NAME = "moonraker-telegram-bot.env" 21 | 22 | # directories 23 | TG_BOT_DIR = Path.home().joinpath("moonraker-telegram-bot") 24 | TG_BOT_ENV = Path.home().joinpath("moonraker-telegram-bot-env") 25 | 26 | # files 27 | TG_BOT_SERVICE_TEMPLATE = MODULE_PATH.joinpath(f"assets/{TG_BOT_SERVICE_NAME}") 28 | TG_BOT_ENV_FILE_TEMPLATE = MODULE_PATH.joinpath(f"assets/{TG_BOT_ENV_FILE_NAME}") 29 | TG_BOT_REQ_FILE = TG_BOT_DIR.joinpath("scripts/requirements.txt") 30 | -------------------------------------------------------------------------------- /kiauh/extensions/telegram_bot/assets/moonraker-telegram-bot.env: -------------------------------------------------------------------------------- 1 | TELEGRAM_BOT_ARGS="%TELEGRAM_BOT_DIR%/bot/main.py -c %CFG% -l %LOG%" -------------------------------------------------------------------------------- /kiauh/extensions/telegram_bot/assets/moonraker-telegram-bot.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Moonraker Telegram Bot SV1 3 | Documentation=https://github.com/nlef/moonraker-telegram-bot/wiki 4 | After=network-online.target 5 | 6 | [Install] 7 | WantedBy=multi-user.target 8 | 9 | [Service] 10 | Type=simple 11 | User=%USER% 12 | WorkingDirectory=%TELEGRAM_BOT_DIR% 13 | EnvironmentFile=%ENV_FILE% 14 | ExecStart=%ENV%/bin/python $TELEGRAM_BOT_ARGS 15 | Restart=always 16 | RestartSec=10 17 | -------------------------------------------------------------------------------- /kiauh/extensions/telegram_bot/metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "metadata": { 3 | "index": 5, 4 | "module": "moonraker_telegram_bot_extension", 5 | "maintained_by": "nlef", 6 | "display_name": "Moonraker Telegram Bot", 7 | "description": ["Control your printer with the Telegram messenger app."], 8 | "project_url": "https://github.com/nlef/moonraker-telegram-bot", 9 | "updates": true 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /kiauh/main.py: -------------------------------------------------------------------------------- 1 | # ======================================================================= # 2 | # Copyright (C) 2020 - 2025 Dominik Willner <th33xitus@gmail.com> # 3 | # # 4 | # This file is part of KIAUH - Klipper Installation And Update Helper # 5 | # https://github.com/dw-0/kiauh # 6 | # # 7 | # This file may be distributed under the terms of the GNU GPLv3 license # 8 | # ======================================================================= # 9 | import io 10 | import sys 11 | 12 | from core.logger import Logger 13 | from core.menus.main_menu import MainMenu 14 | from core.settings.kiauh_settings import KiauhSettings 15 | 16 | 17 | def ensure_encoding() -> None: 18 | if sys.stdout.encoding == "UTF-8" or not isinstance(sys.stdout, io.TextIOWrapper): 19 | return 20 | sys.stdout.reconfigure(encoding="utf-8") 21 | 22 | 23 | def main() -> None: 24 | try: 25 | KiauhSettings() 26 | ensure_encoding() 27 | MainMenu().run() 28 | except KeyboardInterrupt: 29 | Logger.print_ok("\nHappy printing!\n", prefix=False) 30 | -------------------------------------------------------------------------------- /kiauh/procedures/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dw-0/kiauh/33113e72e9a45ac1a4020d409b5932e60e4c77eb/kiauh/procedures/__init__.py -------------------------------------------------------------------------------- /kiauh/utils/__init__.py: -------------------------------------------------------------------------------- 1 | # ======================================================================= # 2 | # Copyright (C) 2020 - 2025 Dominik Willner <th33xitus@gmail.com> # 3 | # # 4 | # This file is part of KIAUH - Klipper Installation And Update Helper # 5 | # https://github.com/dw-0/kiauh # 6 | # # 7 | # This file may be distributed under the terms of the GNU GPLv3 license # 8 | # ======================================================================= # 9 | 10 | from pathlib import Path 11 | 12 | MODULE_PATH = Path(__file__).resolve().parent 13 | -------------------------------------------------------------------------------- /kiauh/utils/config_utils.py: -------------------------------------------------------------------------------- 1 | # ======================================================================= # 2 | # Copyright (C) 2020 - 2025 Dominik Willner <th33xitus@gmail.com> # 3 | # # 4 | # This file is part of KIAUH - Klipper Installation And Update Helper # 5 | # https://github.com/dw-0/kiauh # 6 | # # 7 | # This file may be distributed under the terms of the GNU GPLv3 license # 8 | # ======================================================================= # 9 | from __future__ import annotations 10 | 11 | import shutil 12 | import tempfile 13 | from pathlib import Path 14 | from typing import List, Tuple 15 | 16 | from core.logger import Logger 17 | from core.submodules.simple_config_parser.src.simple_config_parser.simple_config_parser import ( 18 | SimpleConfigParser, 19 | ) 20 | from utils.instance_type import InstanceType 21 | 22 | ConfigOption = Tuple[str, str] 23 | 24 | 25 | def add_config_section( 26 | section: str, 27 | instances: List[InstanceType], 28 | options: List[ConfigOption] | None = None, 29 | ) -> None: 30 | if not instances: 31 | return 32 | 33 | for instance in instances: 34 | cfg_file = instance.cfg_file 35 | Logger.print_status(f"Add section '[{section}]' to '{cfg_file}' ...") 36 | 37 | if not Path(cfg_file).exists(): 38 | Logger.print_warn(f"'{cfg_file}' not found!") 39 | continue 40 | 41 | scp = SimpleConfigParser() 42 | scp.read_file(cfg_file) 43 | if scp.has_section(section): 44 | Logger.print_info("Section already exist. Skipped ...") 45 | continue 46 | 47 | scp.add_section(section) 48 | 49 | if options is not None: 50 | for option in reversed(options): 51 | scp.set_option(section, option[0], option[1]) 52 | 53 | scp.write_file(cfg_file) 54 | 55 | Logger.print_ok("OK!") 56 | 57 | 58 | def add_config_section_at_top(section: str, instances: List[InstanceType]) -> None: 59 | # TODO: this could be implemented natively in SimpleConfigParser 60 | for instance in instances: 61 | tmp_cfg = tempfile.NamedTemporaryFile(mode="w", delete=False) 62 | tmp_cfg_path = Path(tmp_cfg.name) 63 | scp = SimpleConfigParser() 64 | scp.read_file(tmp_cfg_path) 65 | scp.add_section(section) 66 | scp.write_file(tmp_cfg_path) 67 | tmp_cfg.close() 68 | 69 | cfg_file = instance.cfg_file 70 | with open(cfg_file, "r") as org: 71 | org_content = org.readlines() 72 | with open(tmp_cfg_path, "a") as tmp: 73 | tmp.writelines(org_content) 74 | 75 | cfg_file.unlink() 76 | shutil.move(tmp_cfg_path, cfg_file) 77 | 78 | Logger.print_ok("OK!") 79 | 80 | 81 | def remove_config_section( 82 | section: str, instances: List[InstanceType] 83 | ) -> List[InstanceType]: 84 | removed_from: List[instances] = [] 85 | for instance in instances: 86 | cfg_file = instance.cfg_file 87 | Logger.print_status(f"Remove section '[{section}]' from '{cfg_file}' ...") 88 | 89 | if not Path(cfg_file).exists(): 90 | Logger.print_warn(f"'{cfg_file}' not found!") 91 | continue 92 | 93 | scp = SimpleConfigParser() 94 | scp.read_file(cfg_file) 95 | if not scp.has_section(section): 96 | Logger.print_info("Section does not exist. Skipped ...") 97 | continue 98 | 99 | scp.remove_section(section) 100 | scp.write_file(cfg_file) 101 | 102 | removed_from.append(instance) 103 | Logger.print_ok("OK!") 104 | 105 | return removed_from 106 | -------------------------------------------------------------------------------- /kiauh/utils/instance_type.py: -------------------------------------------------------------------------------- 1 | # ======================================================================= # 2 | # Copyright (C) 2020 - 2025 Dominik Willner <th33xitus@gmail.com> # 3 | # # 4 | # This file is part of KIAUH - Klipper Installation And Update Helper # 5 | # https://github.com/dw-0/kiauh # 6 | # # 7 | # This file may be distributed under the terms of the GNU GPLv3 license # 8 | # ======================================================================= # 9 | 10 | from typing import TypeVar 11 | 12 | from components.klipper.klipper import Klipper 13 | from components.moonraker.moonraker import Moonraker 14 | from extensions.obico.moonraker_obico import MoonrakerObico 15 | from extensions.octoeverywhere.octoeverywhere import Octoeverywhere 16 | from extensions.octoapp.octoapp import Octoapp 17 | from extensions.telegram_bot.moonraker_telegram_bot import MoonrakerTelegramBot 18 | 19 | InstanceType = TypeVar( 20 | "InstanceType", 21 | Klipper, 22 | Moonraker, 23 | MoonrakerTelegramBot, 24 | MoonrakerObico, 25 | Octoeverywhere, 26 | Octoapp, 27 | ) 28 | -------------------------------------------------------------------------------- /kiauh/utils/instance_utils.py: -------------------------------------------------------------------------------- 1 | # ======================================================================= # 2 | # Copyright (C) 2020 - 2025 Dominik Willner <th33xitus@gmail.com> # 3 | # # 4 | # This file is part of KIAUH - Klipper Installation And Update Helper # 5 | # https://github.com/dw-0/kiauh # 6 | # # 7 | # This file may be distributed under the terms of the GNU GPLv3 license # 8 | # ======================================================================= # 9 | from __future__ import annotations 10 | 11 | import re 12 | from pathlib import Path 13 | from typing import List 14 | 15 | from core.constants import SYSTEMD 16 | from core.instance_manager.base_instance import SUFFIX_BLACKLIST 17 | from utils.instance_type import InstanceType 18 | 19 | 20 | def get_instances( 21 | instance_type: type, suffix_blacklist: List[str] = SUFFIX_BLACKLIST 22 | ) -> List[InstanceType]: 23 | from utils.common import convert_camelcase_to_kebabcase 24 | 25 | if not isinstance(instance_type, type): 26 | raise ValueError("instance_type must be a class") 27 | 28 | name = convert_camelcase_to_kebabcase(instance_type.__name__) 29 | pattern = re.compile(f"^{name}(-[0-9a-zA-Z]+)?.servicequot;) 30 | 31 | service_list = [ 32 | Path(SYSTEMD, service) 33 | for service in SYSTEMD.iterdir() 34 | if pattern.search(service.name) 35 | and not any(s in service.name for s in suffix_blacklist) 36 | ] 37 | 38 | instance_list = [ 39 | instance_type(get_instance_suffix(name, service)) for service in service_list 40 | ] 41 | 42 | def _sort_instance_list(suffix: int | str | None): 43 | if suffix is None: 44 | return 45 | elif isinstance(suffix, str) and suffix.isdigit(): 46 | return f"{int(suffix):04}" 47 | else: 48 | return suffix 49 | 50 | return sorted(instance_list, key=lambda x: _sort_instance_list(x.suffix)) 51 | 52 | 53 | def get_instance_suffix(name: str, file_path: Path) -> str: 54 | # to get the suffix of the instance, we remove the name of the instance from 55 | # the file name, if the remaining part an empty string we return it 56 | # otherwise there is and hyphen left, and we return the part after the hyphen 57 | suffix = file_path.stem[len(name) :] 58 | return suffix[1:] if suffix else "" 59 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | requires-python = ">=3.8" 3 | 4 | [project.optional-dependencies] 5 | dev=["ruff", "pyright"] 6 | 7 | [tool.ruff] 8 | required-version = ">=0.9.10" 9 | respect-gitignore = true 10 | exclude = [".git",".github", "./docs", "kiauh/core/submodules"] 11 | line-length = 88 12 | indent-width = 4 13 | output-format = "full" 14 | target-version = "py38" 15 | 16 | [tool.ruff.format] 17 | indent-style = "space" 18 | line-ending = "lf" 19 | quote-style = "double" 20 | 21 | [tool.ruff.lint] 22 | extend-select = ["I"] 23 | -------------------------------------------------------------------------------- /pyrightconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "pythonVersion": "3.8", 3 | "pythonPlatform": "Linux", 4 | "typeCheckingMode": "standard", 5 | "venvPath": "./.kiauh-env" 6 | } 7 | -------------------------------------------------------------------------------- /requirements-dev.txt: -------------------------------------------------------------------------------- 1 | ruff (>=0.9.10) 2 | pyright 3 | -------------------------------------------------------------------------------- /resources/autocommit.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ##################################################################### 4 | ### Please set the paths accordingly. In case you don't have all ### 5 | ### the listed folders, just keep that line commented out. ### 6 | ##################################################################### 7 | ### Path to your config folder you want to back up 8 | #config_folder=~/klipper_config 9 | 10 | ### Path to your Klipper folder, by default that is '~/klipper' 11 | #klipper_folder=~/klipper 12 | 13 | ### Path to your Moonraker folder, by default that is '~/moonraker' 14 | #moonraker_folder=~/moonraker 15 | 16 | ### Path to your Mainsail folder, by default that is '~/mainsail' 17 | #mainsail_folder=~/mainsail 18 | 19 | ### Path to your Fluidd folder, by default that is '~/fluidd' 20 | #fluidd_folder=~/fluidd 21 | 22 | ##################################################################### 23 | ##################################################################### 24 | 25 | 26 | ##################################################################### 27 | ################ !!! DO NOT EDIT BELOW THIS LINE !!! ################ 28 | ##################################################################### 29 | grab_version() { 30 | local klipper_commit moonraker_commit 31 | local mainsail_ver fluidd_ver 32 | 33 | if [[ -n ${klipper_folder} ]]; then 34 | cd "${klipper_folder}" 35 | klipper_commit=$(git rev-parse --short=7 HEAD) 36 | m1="Klipper on commit: ${klipper_commit}" 37 | fi 38 | if [[ -n ${moonraker_folder} ]]; then 39 | cd "${moonraker_folder}" 40 | moonraker_commit=$(git rev-parse --short=7 HEAD) 41 | m2="Moonraker on commit: ${moonraker_commit}" 42 | fi 43 | if [[ -n ${mainsail_folder} ]]; then 44 | mainsail_ver=$(head -n 1 "${mainsail_folder}/.version") 45 | m3="Mainsail version: ${mainsail_ver}" 46 | fi 47 | if [[ -n ${fluidd_folder} ]]; then 48 | fluidd_ver=$(head -n 1 "${fluidd_folder}/.version") 49 | m4="Fluidd version: ${fluidd_ver}" 50 | fi 51 | } 52 | 53 | push_config() { 54 | local current_date 55 | 56 | cd "${config_folder}" || exit 1 57 | git pull 58 | git add . 59 | current_date=$(date +"%Y-%m-%d %T") 60 | git commit -m "Autocommit from ${current_date}" -m "${m1}" -m "${m2}" -m "${m3}" -m "${m4}" 61 | git push 62 | } 63 | 64 | grab_version 65 | push_config 66 | -------------------------------------------------------------------------------- /resources/common_vars.conf: -------------------------------------------------------------------------------- 1 | # /etc/nginx/conf.d/common_vars.conf 2 | 3 | map $http_upgrade $connection_upgrade { 4 | default upgrade; 5 | '' close; 6 | } -------------------------------------------------------------------------------- /resources/example.printer.cfg: -------------------------------------------------------------------------------- 1 | [mcu] 2 | serial: /dev/serial/by-id/<your-mcu-id> 3 | 4 | [virtual_sdcard] 5 | path: %GCODES_DIR% 6 | on_error_gcode: CANCEL_PRINT 7 | 8 | [printer] 9 | kinematics: none 10 | max_velocity: 1000 11 | max_accel: 1000 12 | -------------------------------------------------------------------------------- /resources/fluidd: -------------------------------------------------------------------------------- 1 | # /etc/nginx/sites-available/fluidd 2 | 3 | server { 4 | listen 80; 5 | 6 | access_log /var/log/nginx/fluidd-access.log; 7 | error_log /var/log/nginx/fluidd-error.log; 8 | 9 | # disable this section on smaller hardware like a pi zero 10 | gzip on; 11 | gzip_vary on; 12 | gzip_proxied any; 13 | gzip_proxied expired no-cache no-store private auth; 14 | gzip_comp_level 4; 15 | gzip_buffers 16 8k; 16 | gzip_http_version 1.1; 17 | gzip_types text/plain text/css text/xml text/javascript application/javascript application/x-javascript application/json application/xml; 18 | 19 | # web_path from fluidd static files 20 | root /home/pi/fluidd; 21 | 22 | index index.html; 23 | server_name _; 24 | 25 | # disable max upload size checks 26 | client_max_body_size 0; 27 | 28 | # disable proxy request buffering 29 | proxy_request_buffering off; 30 | 31 | location / { 32 | try_files $uri $uri/ /index.html; 33 | } 34 | 35 | location = /index.html { 36 | add_header Cache-Control "no-store, no-cache, must-revalidate"; 37 | } 38 | 39 | location /websocket { 40 | proxy_pass http://apiserver/websocket; 41 | proxy_http_version 1.1; 42 | proxy_set_header Upgrade $http_upgrade; 43 | proxy_set_header Connection $connection_upgrade; 44 | proxy_set_header Host $http_host; 45 | proxy_set_header X-Real-IP $remote_addr; 46 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 47 | proxy_read_timeout 86400; 48 | } 49 | 50 | location ~ ^/(printer|api|access|machine|server)/ { 51 | proxy_pass http://apiserver$request_uri; 52 | proxy_http_version 1.1; 53 | proxy_set_header Upgrade $http_upgrade; 54 | proxy_set_header Host $http_host; 55 | proxy_set_header X-Real-IP $remote_addr; 56 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 57 | proxy_set_header X-Scheme $scheme; 58 | proxy_read_timeout 600; 59 | } 60 | 61 | location /webcam/ { 62 | postpone_output 0; 63 | proxy_buffering off; 64 | proxy_ignore_headers X-Accel-Buffering; 65 | access_log off; 66 | error_log off; 67 | proxy_pass http://mjpgstreamer1/; 68 | } 69 | 70 | location /webcam2/ { 71 | postpone_output 0; 72 | proxy_buffering off; 73 | proxy_ignore_headers X-Accel-Buffering; 74 | access_log off; 75 | error_log off; 76 | proxy_pass http://mjpgstreamer2/; 77 | } 78 | 79 | location /webcam3/ { 80 | postpone_output 0; 81 | proxy_buffering off; 82 | proxy_ignore_headers X-Accel-Buffering; 83 | access_log off; 84 | error_log off; 85 | proxy_pass http://mjpgstreamer3/; 86 | } 87 | 88 | location /webcam4/ { 89 | postpone_output 0; 90 | proxy_buffering off; 91 | proxy_ignore_headers X-Accel-Buffering; 92 | access_log off; 93 | error_log off; 94 | proxy_pass http://mjpgstreamer4/; 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /resources/gcode_shell_command.py: -------------------------------------------------------------------------------- 1 | # Run a shell command via gcode 2 | # 3 | # Copyright (C) 2019 Eric Callahan <arksine.code@gmail.com> 4 | # 5 | # This file may be distributed under the terms of the GNU GPLv3 license. 6 | import os 7 | import shlex 8 | import subprocess 9 | import logging 10 | 11 | 12 | class ShellCommand: 13 | def __init__(self, config): 14 | self.name = config.get_name().split()[-1] 15 | self.printer = config.get_printer() 16 | self.gcode = self.printer.lookup_object("gcode") 17 | cmd = config.get("command") 18 | cmd = os.path.expanduser(cmd) 19 | self.command = shlex.split(cmd) 20 | self.timeout = config.getfloat("timeout", 2.0, above=0.0) 21 | self.verbose = config.getboolean("verbose", True) 22 | self.proc_fd = None 23 | self.partial_output = "" 24 | self.gcode.register_mux_command( 25 | "RUN_SHELL_COMMAND", 26 | "CMD", 27 | self.name, 28 | self.cmd_RUN_SHELL_COMMAND, 29 | desc=self.cmd_RUN_SHELL_COMMAND_help, 30 | ) 31 | 32 | def _process_output(self, eventime): 33 | if self.proc_fd is None: 34 | return 35 | try: 36 | data = os.read(self.proc_fd, 4096) 37 | except Exception: 38 | pass 39 | data = self.partial_output + data.decode() 40 | if "\n" not in data: 41 | self.partial_output = data 42 | return 43 | elif data[-1] != "\n": 44 | split = data.rfind("\n") + 1 45 | self.partial_output = data[split:] 46 | data = data[:split] 47 | else: 48 | self.partial_output = "" 49 | self.gcode.respond_info(data) 50 | 51 | cmd_RUN_SHELL_COMMAND_help = "Run a linux shell command" 52 | 53 | def cmd_RUN_SHELL_COMMAND(self, params): 54 | gcode_params = params.get("PARAMS", "") 55 | gcode_params = shlex.split(gcode_params) 56 | reactor = self.printer.get_reactor() 57 | try: 58 | proc = subprocess.Popen( 59 | self.command + gcode_params, 60 | stdout=subprocess.PIPE, 61 | stderr=subprocess.STDOUT, 62 | ) 63 | except Exception: 64 | logging.exception("shell_command: Command {%s} failed" % (self.name)) 65 | raise self.gcode.error("Error running command {%s}" % (self.name)) 66 | if self.verbose: 67 | self.proc_fd = proc.stdout.fileno() 68 | self.gcode.respond_info("Running Command {%s}...:" % (self.name)) 69 | hdl = reactor.register_fd(self.proc_fd, self._process_output) 70 | eventtime = reactor.monotonic() 71 | endtime = eventtime + self.timeout 72 | complete = False 73 | while eventtime < endtime: 74 | eventtime = reactor.pause(eventtime + 0.05) 75 | if proc.poll() is not None: 76 | complete = True 77 | break 78 | if not complete: 79 | proc.terminate() 80 | if self.verbose: 81 | if self.partial_output: 82 | self.gcode.respond_info(self.partial_output) 83 | self.partial_output = "" 84 | if complete: 85 | msg = "Command {%s} finished\n" % (self.name) 86 | else: 87 | msg = "Command {%s} timed out" % (self.name) 88 | self.gcode.respond_info(msg) 89 | reactor.unregister_fd(hdl) 90 | self.proc_fd = None 91 | 92 | 93 | def load_config_prefix(config): 94 | return ShellCommand(config) 95 | -------------------------------------------------------------------------------- /resources/klipper.env: -------------------------------------------------------------------------------- 1 | KLIPPER_ARGS="%KLIPPER_DIR%/klippy/klippy.py %CFG% -I %PRINTER% -l %LOG% -a %UDS%" -------------------------------------------------------------------------------- /resources/klipper.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Klipper 3D Printer Firmware SV1 3 | Documentation=https://www.klipper3d.org/ 4 | After=network-online.target 5 | Wants=udev.target 6 | 7 | [Install] 8 | WantedBy=multi-user.target 9 | 10 | [Service] 11 | Type=simple 12 | User=%USER% 13 | RemainAfterExit=yes 14 | WorkingDirectory=%KLIPPER_DIR% 15 | EnvironmentFile=%ENV_FILE% 16 | ExecStart=%ENV%/bin/python $KLIPPER_ARGS 17 | Restart=always 18 | RestartSec=10 19 | -------------------------------------------------------------------------------- /resources/mainsail: -------------------------------------------------------------------------------- 1 | # /etc/nginx/sites-available/mainsail 2 | 3 | server { 4 | listen 80; 5 | 6 | access_log /var/log/nginx/mainsail-access.log; 7 | error_log /var/log/nginx/mainsail-error.log; 8 | 9 | # disable this section on smaller hardware like a pi zero 10 | gzip on; 11 | gzip_vary on; 12 | gzip_proxied any; 13 | gzip_proxied expired no-cache no-store private auth; 14 | gzip_comp_level 4; 15 | gzip_buffers 16 8k; 16 | gzip_http_version 1.1; 17 | gzip_types text/plain text/css text/xml text/javascript application/javascript application/x-javascript application/json application/xml; 18 | 19 | # web_path from mainsail static files 20 | root /home/pi/mainsail; 21 | 22 | index index.html; 23 | server_name _; 24 | 25 | # disable max upload size checks 26 | client_max_body_size 0; 27 | 28 | # disable proxy request buffering 29 | proxy_request_buffering off; 30 | 31 | location / { 32 | try_files $uri $uri/ /index.html; 33 | } 34 | 35 | location = /index.html { 36 | add_header Cache-Control "no-store, no-cache, must-revalidate"; 37 | } 38 | 39 | location /websocket { 40 | proxy_pass http://apiserver/websocket; 41 | proxy_http_version 1.1; 42 | proxy_set_header Upgrade $http_upgrade; 43 | proxy_set_header Connection $connection_upgrade; 44 | proxy_set_header Host $http_host; 45 | proxy_set_header X-Real-IP $remote_addr; 46 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 47 | proxy_read_timeout 86400; 48 | } 49 | 50 | location ~ ^/(printer|api|access|machine|server)/ { 51 | proxy_pass http://apiserver$request_uri; 52 | proxy_http_version 1.1; 53 | proxy_set_header Upgrade $http_upgrade; 54 | proxy_set_header Host $http_host; 55 | proxy_set_header X-Real-IP $remote_addr; 56 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 57 | proxy_set_header X-Scheme $scheme; 58 | proxy_read_timeout 600; 59 | } 60 | 61 | location /webcam/ { 62 | postpone_output 0; 63 | proxy_buffering off; 64 | proxy_ignore_headers X-Accel-Buffering; 65 | access_log off; 66 | error_log off; 67 | proxy_pass http://mjpgstreamer1/; 68 | } 69 | 70 | location /webcam2/ { 71 | postpone_output 0; 72 | proxy_buffering off; 73 | proxy_ignore_headers X-Accel-Buffering; 74 | access_log off; 75 | error_log off; 76 | proxy_pass http://mjpgstreamer2/; 77 | } 78 | 79 | location /webcam3/ { 80 | postpone_output 0; 81 | proxy_buffering off; 82 | proxy_ignore_headers X-Accel-Buffering; 83 | access_log off; 84 | error_log off; 85 | proxy_pass http://mjpgstreamer3/; 86 | } 87 | 88 | location /webcam4/ { 89 | postpone_output 0; 90 | proxy_buffering off; 91 | proxy_ignore_headers X-Accel-Buffering; 92 | access_log off; 93 | error_log off; 94 | proxy_pass http://mjpgstreamer4/; 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /resources/mjpg-streamer/webcam.txt: -------------------------------------------------------------------------------- 1 | ### Windows users: To edit this file use Notepad++, VSCode, Atom or SublimeText. 2 | ### Do not use Notepad or WordPad. 3 | 4 | ### MacOSX users: If you use Textedit to edit this file make sure to use 5 | ### "plain text format" and "disable smart quotes" in "Textedit > Preferences" 6 | 7 | ### Configure which camera to use 8 | # 9 | # Available options are: 10 | # - auto: tries first usb webcam, if that's not available tries raspi cam 11 | # - usb: only tries usb webcam 12 | # - raspi: only tries raspi cam 13 | # 14 | # Defaults to auto 15 | # 16 | #camera="auto" 17 | 18 | ### Additional options to supply to MJPG Streamer for the USB camera 19 | # 20 | # See https://faq.octoprint.org/mjpg-streamer-config for available options 21 | # 22 | # Defaults to a resolution of 640x480 px and a framerate of 10 fps 23 | # 24 | #camera_usb_options="-r 640x480 -f 10" 25 | 26 | ### Additional webcam devices known to cause problems with -f 27 | # 28 | # Apparently there a some devices out there that with the current 29 | # mjpg_streamer release do not support the -f parameter (for specifying 30 | # the capturing framerate) and will just refuse to output an image if it 31 | # is supplied. 32 | # 33 | # The webcam daemon will detect those devices by their USB Vendor and Product 34 | # ID and remove the -f parameter from the options provided to mjpg_streamer. 35 | # 36 | # By default, this is done for the following devices: 37 | # Logitech C170 (046d:082b) 38 | # GEMBIRD (1908:2310) 39 | # Genius F100 (0458:708c) 40 | # Cubeternet GL-UPC822 UVC WebCam (1e4e:0102) 41 | # 42 | # Using the following option it is possible to add additional devices. If 43 | # your webcam happens to show above symptoms, try determining your cam's 44 | # vendor and product id via lsusb, activating the line below by removing # and 45 | # adding it, e.g. for two broken cameras "aabb:ccdd" and "aabb:eeff" 46 | # 47 | # additional_brokenfps_usb_devices=("aabb:ccdd" "aabb:eeff") 48 | # 49 | # 50 | #additional_brokenfps_usb_devices=() 51 | 52 | ### Additional options to supply to MJPG Streamer for the RasPi Cam 53 | # 54 | # See https://faq.octoprint.org/mjpg-streamer-config for available options 55 | # 56 | # Defaults to 10fps 57 | # 58 | #camera_raspi_options="-fps 10" 59 | 60 | ### Configuration of camera HTTP output 61 | # 62 | # Usually you should NOT need to change this at all! Only touch if you 63 | # know what you are doing and what the parameters mean. 64 | # 65 | # Below settings are used in the mjpg-streamer call like this: 66 | # 67 | # -o "output_http.so -w $camera_http_webroot $camera_http_options" 68 | # 69 | # Current working directory is the mjpg-streamer base directory. 70 | # 71 | #camera_http_webroot="./www-mainsail" 72 | #camera_http_options="-n" 73 | 74 | ### EXPERIMENTAL 75 | # Support for different streamer types. 76 | # 77 | # Available options: 78 | # mjpeg [default] - stable MJPG-streamer 79 | #camera_streamer=mjpeg 80 | -------------------------------------------------------------------------------- /resources/mjpg-streamer/webcamd.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Starts mjpg-streamer on startup 3 | After=network.target 4 | 5 | [Install] 6 | WantedBy=multi-user.target 7 | 8 | [Service] 9 | Type=forking 10 | User=%USER% 11 | WorkingDirectory=/usr/local/bin 12 | StandardOutput=append:/var/log/webcamd.log 13 | StandardError=append:/var/log/webcamd.log 14 | ExecStart=/usr/local/bin/webcamd 15 | Restart=always -------------------------------------------------------------------------------- /resources/moonraker-telegram-bot.env: -------------------------------------------------------------------------------- 1 | TELEGRAM_BOT_ARGS="%TELEGRAM_BOT_DIR%/bot/main.py -c %CFG% -l %LOG%" -------------------------------------------------------------------------------- /resources/moonraker-telegram-bot.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Moonraker Telegram Bot SV1 %INST% 3 | Documentation=https://github.com/nlef/moonraker-telegram-bot/wiki 4 | After=network-online.target 5 | 6 | [Install] 7 | WantedBy=multi-user.target 8 | 9 | [Service] 10 | Type=simple 11 | User=%USER% 12 | WorkingDirectory=%TELEGRAM_BOT_DIR% 13 | EnvironmentFile=%ENV_FILE% 14 | ExecStart=%ENV%/bin/python $TELEGRAM_BOT_ARGS 15 | Restart=always 16 | RestartSec=10 17 | -------------------------------------------------------------------------------- /resources/moonraker.conf: -------------------------------------------------------------------------------- 1 | [server] 2 | host: 0.0.0.0 3 | port: %PORT% 4 | klippy_uds_address: %UDS% 5 | 6 | [authorization] 7 | trusted_clients: 8 | %LAN% 9 | 10.0.0.0/8 10 | 127.0.0.0/8 11 | 169.254.0.0/16 12 | 172.16.0.0/12 13 | 192.168.0.0/16 14 | FE80::/10 15 | ::1/128 16 | cors_domains: 17 | *.lan 18 | *.local 19 | *.internal 20 | *://localhost 21 | *://localhost:* 22 | *://my.mainsail.xyz 23 | *://app.fluidd.xyz 24 | 25 | [octoprint_compat] 26 | 27 | [history] 28 | 29 | [update_manager] 30 | channel: dev 31 | refresh_interval: 168 32 | -------------------------------------------------------------------------------- /resources/moonraker.env: -------------------------------------------------------------------------------- 1 | MOONRAKER_ARGS="%MOONRAKER_DIR%/moonraker/moonraker.py -d %PRINTER_DATA%" -------------------------------------------------------------------------------- /resources/moonraker.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=API Server for Klipper SV1 %INST% 3 | Documentation=https://moonraker.readthedocs.io/ 4 | Requires=network-online.target 5 | After=network-online.target 6 | 7 | [Install] 8 | WantedBy=multi-user.target 9 | 10 | [Service] 11 | Type=simple 12 | User=%USER% 13 | SupplementaryGroups=moonraker-admin 14 | RemainAfterExit=yes 15 | WorkingDirectory=%MOONRAKER_DIR% 16 | EnvironmentFile=%ENV_FILE% 17 | ExecStart=%ENV%/bin/python $MOONRAKER_ARGS 18 | Restart=always 19 | RestartSec=10 -------------------------------------------------------------------------------- /resources/screenshots/kiauh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dw-0/kiauh/33113e72e9a45ac1a4020d409b5932e60e4c77eb/resources/screenshots/kiauh.png -------------------------------------------------------------------------------- /resources/screenshots/rpi_imager1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dw-0/kiauh/33113e72e9a45ac1a4020d409b5932e60e4c77eb/resources/screenshots/rpi_imager1.png -------------------------------------------------------------------------------- /resources/screenshots/rpi_imager2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dw-0/kiauh/33113e72e9a45ac1a4020d409b5932e60e4c77eb/resources/screenshots/rpi_imager2.png -------------------------------------------------------------------------------- /resources/shell_command.cfg: -------------------------------------------------------------------------------- 1 | [gcode_shell_command hello_world] 2 | command: echo hello world 3 | timeout: 2. 4 | verbose: True 5 | [gcode_macro HELLO_WORLD] 6 | gcode: 7 | RUN_SHELL_COMMAND CMD=hello_world -------------------------------------------------------------------------------- /resources/upstreams.conf: -------------------------------------------------------------------------------- 1 | # /etc/nginx/conf.d/upstreams.conf 2 | upstream apiserver { 3 | ip_hash; 4 | server 127.0.0.1:7125; 5 | } 6 | 7 | upstream mjpgstreamer1 { 8 | ip_hash; 9 | server 127.0.0.1:8080; 10 | } 11 | 12 | upstream mjpgstreamer2 { 13 | ip_hash; 14 | server 127.0.0.1:8081; 15 | } 16 | 17 | upstream mjpgstreamer3 { 18 | ip_hash; 19 | server 127.0.0.1:8082; 20 | } 21 | 22 | upstream mjpgstreamer4 { 23 | ip_hash; 24 | server 127.0.0.1:8083; 25 | } -------------------------------------------------------------------------------- /scripts/globals.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | #=======================================================================# 4 | # Copyright (C) 2020 - 2024 Dominik Willner <th33xitus@gmail.com> # 5 | # # 6 | # This file is part of KIAUH - Klipper Installation And Update Helper # 7 | # https://github.com/dw-0/kiauh # 8 | # # 9 | # This file may be distributed under the terms of the GNU GPLv3 license # 10 | #=======================================================================# 11 | 12 | # shellcheck disable=SC2034 13 | set -e 14 | 15 | function set_globals() { 16 | #=================== SYSTEM ===================# 17 | SYSTEMD="/etc/systemd/system" 18 | INITD="/etc/init.d" 19 | ETCDEF="/etc/default" 20 | 21 | #=================== KIAUH ====================# 22 | green=$(echo -en "\e[92m") 23 | yellow=$(echo -en "\e[93m") 24 | magenta=$(echo -en "\e[35m") 25 | red=$(echo -en "\e[91m") 26 | cyan=$(echo -en "\e[96m") 27 | white=$(echo -en "\e[39m") 28 | INI_FILE="${HOME}/.kiauh.ini" 29 | LOGFILE="/tmp/kiauh.log" 30 | RESOURCES="${KIAUH_SRCDIR}/resources" 31 | BACKUP_DIR="${HOME}/kiauh-backups" 32 | 33 | #================== KLIPPER ===================# 34 | KLIPPY_ENV="${HOME}/klippy-env" 35 | KLIPPER_DIR="${HOME}/klipper" 36 | KLIPPER_REPO="https://github.com/Klipper3d/klipper.git" 37 | 38 | #================= MOONRAKER ==================# 39 | MOONRAKER_ENV="${HOME}/moonraker-env" 40 | MOONRAKER_DIR="${HOME}/moonraker" 41 | MOONRAKER_REPO="https://github.com/Arksine/moonraker.git" 42 | 43 | #================= MAINSAIL ===================# 44 | MAINSAIL_DIR="${HOME}/mainsail" 45 | 46 | #================== FLUIDD ====================# 47 | FLUIDD_DIR="${HOME}/fluidd" 48 | 49 | #=============== KLIPPERSCREEN ================# 50 | KLIPPERSCREEN_ENV="${HOME}/.KlipperScreen-env" 51 | KLIPPERSCREEN_DIR="${HOME}/KlipperScreen" 52 | KLIPPERSCREEN_REPO="https://github.com/jordanruthe/KlipperScreen.git" 53 | 54 | #========== MOONRAKER-TELEGRAM-BOT ============# 55 | TELEGRAM_BOT_ENV="${HOME}/moonraker-telegram-bot-env" 56 | TELEGRAM_BOT_DIR="${HOME}/moonraker-telegram-bot" 57 | TELEGRAM_BOT_REPO="https://github.com/nlef/moonraker-telegram-bot.git" 58 | 59 | #=============== PRETTY-GCODE =================# 60 | PGC_DIR="${HOME}/pgcode" 61 | PGC_REPO="https://github.com/Kragrathea/pgcode" 62 | 63 | #================== NGINX =====================# 64 | NGINX_SA="/etc/nginx/sites-available" 65 | NGINX_SE="/etc/nginx/sites-enabled" 66 | NGINX_CONFD="/etc/nginx/conf.d" 67 | 68 | #=============== MOONRAKER-OBICO ================# 69 | MOONRAKER_OBICO_DIR="${HOME}/moonraker-obico" 70 | MOONRAKER_OBICO_REPO="https://github.com/TheSpaghettiDetective/moonraker-obico.git" 71 | 72 | #=============== OCTOEVERYWHERE ================# 73 | OCTOEVERYWHERE_ENV="${HOME}/octoeverywhere-env" 74 | OCTOEVERYWHERE_DIR="${HOME}/octoeverywhere" 75 | OCTOEVERYWHERE_REPO="https://github.com/QuinnDamerell/OctoPrint-OctoEverywhere.git" 76 | 77 | #=============== Crowsnest ================# 78 | CROWSNEST_DIR="${HOME}/crowsnest" 79 | CROWSNEST_REPO="https://github.com/mainsail-crew/crowsnest.git" 80 | 81 | #=============== Mobileraker ================# 82 | MOBILERAKER_ENV="${HOME}/mobileraker-env" 83 | MOBILERAKER_DIR="${HOME}/mobileraker_companion" 84 | MOBILERAKER_REPO="https://github.com/Clon1998/mobileraker_companion.git" 85 | 86 | #=============== OCTOAPP ================# 87 | OCTOAPP_ENV="${HOME}/octoapp-env" 88 | OCTOAPP_DIR="${HOME}/octoapp" 89 | OCTOAPP_REPO="https://github.com/crysxd/OctoApp-Plugin.git" 90 | 91 | #=============== Spoolman ================# 92 | SPOOLMAN_DIR="${HOME}/Spoolman" 93 | SPOOLMAN_DB_DIR="${HOME}/.local/share/spoolman" 94 | SPOOLMAN_REPO="https://api.github.com/repos/Donkie/Spoolman/releases/latest" 95 | } 96 | -------------------------------------------------------------------------------- /scripts/rollback.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | #=======================================================================# 4 | # Copyright (C) 2020 - 2024 Dominik Willner <th33xitus@gmail.com> # 5 | # # 6 | # This file is part of KIAUH - Klipper Installation And Update Helper # 7 | # https://github.com/dw-0/kiauh # 8 | # # 9 | # This file may be distributed under the terms of the GNU GPLv3 license # 10 | #=======================================================================# 11 | 12 | set -e 13 | 14 | function rollback_menu() { 15 | top_border 16 | echo -e "| $(title_msg "~~~~~~~~~~~~~ [ Rollback Menu ] ~~~~~~~~~~~~~") |" 17 | hr 18 | echo -e "| If serious errors occured after updating Klipper or |" 19 | echo -e "| Moonraker, you can use this menu to try and reset the |" 20 | echo -e "| repository to an earlier state. |" 21 | hr 22 | echo -e "| 1) Rollback Klipper |" 23 | echo -e "| 2) Rollback Moonraker |" 24 | back_footer 25 | 26 | local action 27 | while true; do 28 | read -p "${cyan}###### Perform action:${white} " action 29 | case "${action}" in 30 | 1) 31 | select_msg "Klipper" 32 | rollback_component "klipper" 33 | break;; 34 | 2) 35 | select_msg "Moonraker" 36 | rollback_component "moonraker" 37 | break;; 38 | B|b) 39 | clear; advanced_menu; break;; 40 | *) 41 | error_msg "Invalid command!";; 42 | esac 43 | done 44 | } 45 | 46 | function rollback_component() { 47 | local component=${1} 48 | 49 | if [[ ! -d "${HOME}/${component}" ]]; then 50 | print_error "Rollback not possible! Missing installation?" 51 | return 52 | fi 53 | 54 | echo 55 | top_border 56 | echo -e "| Please select how many commits you want to revert. |" 57 | echo -e "| Consider using the information provided by the GitHub |" 58 | echo -e "| commit history to decide how many commits to revert. |" 59 | blank_line 60 | echo -e "| ${red}Warning:${white} |" 61 | echo -e "| ${red}Do not proceed if you are currently in the progress${white} |" 62 | echo -e "| ${red}of printing! Proceeding WILL terminate that print!${white} |" 63 | back_footer 64 | 65 | local count 66 | while true; do 67 | read -p "${cyan}###### Revert this amount of commits:${white} " count 68 | if [[ -n ${count} ]] && (( count > 0 )); then 69 | status_msg "Revert ${component^} by ${count} commits ..." 70 | cd "${HOME}/${component}" 71 | if git reset --hard HEAD~"${count}"; then 72 | do_action_service "restart" "${component}" 73 | print_confirm "${component^} was successfully reset!" 74 | else 75 | print_error "Reverting ${component^} failed! Please see the console output above." 76 | fi 77 | break 78 | elif [[ ${count} == "B" || ${count} == "b" ]]; then 79 | clear && print_header && break 80 | else 81 | error_msg "Invalid command!" 82 | fi 83 | done 84 | rollback_menu 85 | } 86 | -------------------------------------------------------------------------------- /scripts/ui/advanced_menu.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | #=======================================================================# 4 | # Copyright (C) 2020 - 2024 Dominik Willner <th33xitus@gmail.com> # 5 | # # 6 | # This file is part of KIAUH - Klipper Installation And Update Helper # 7 | # https://github.com/dw-0/kiauh # 8 | # # 9 | # This file may be distributed under the terms of the GNU GPLv3 license # 10 | #=======================================================================# 11 | 12 | set -e 13 | 14 | function advanced_ui() { 15 | top_border 16 | echo -e "| ${yellow}~~~~~~~~~~~~~ [ Advanced Menu ] ~~~~~~~~~~~~~${white} |" 17 | hr 18 | echo -e "| Klipper & API: | Mainsail: |" 19 | echo -e "| 1) [Rollback] | 6) [Theme installer] |" 20 | echo -e "| | |" 21 | echo -e "| Firmware: | System: |" 22 | echo -e "| 2) [Build only] | 7) [Change hostname] |" 23 | echo -e "| 3) [Flash only] | |" 24 | echo -e "| 4) [Build + Flash] | Extras: |" 25 | echo -e "| 5) [Get MCU ID] | 8) [G-Code Shell Command] |" 26 | back_footer 27 | } 28 | 29 | function advanced_menu() { 30 | do_action "" "advanced_ui" 31 | 32 | local action 33 | while true; do 34 | read -p "${cyan}####### Perform action:${white} " action 35 | case "${action}" in 36 | 1) 37 | do_action "rollback_menu" "advanced_menu";; 38 | 2) 39 | do_action "build_fw" "advanced_ui";; 40 | 3) 41 | clear && print_header 42 | do_action "init_flash_process" "advanced_ui";; 43 | 4) 44 | clear && print_header 45 | status_msg "Please wait..." 46 | build_fw && init_flash_process 47 | advanced_ui;; 48 | 5) 49 | clear && print_header 50 | select_mcu_connection 51 | print_detected_mcu_to_screen 52 | advanced_ui;; 53 | 6) 54 | do_action "ms_theme_installer_menu";; 55 | 7) 56 | clear 57 | print_header 58 | set_custom_hostname 59 | advanced_ui;; 60 | 8) 61 | do_action "setup_gcode_shell_command" "advanced_ui";; 62 | B|b) 63 | clear; main_menu; break;; 64 | *) 65 | deny_action "advanced_ui";; 66 | esac 67 | done 68 | advanced_menu 69 | } 70 | -------------------------------------------------------------------------------- /scripts/ui/backup_menu.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | #=======================================================================# 4 | # Copyright (C) 2020 - 2024 Dominik Willner <th33xitus@gmail.com> # 5 | # # 6 | # This file is part of KIAUH - Klipper Installation And Update Helper # 7 | # https://github.com/dw-0/kiauh # 8 | # # 9 | # This file may be distributed under the terms of the GNU GPLv3 license # 10 | #=======================================================================# 11 | 12 | set -e 13 | 14 | function backup_ui() { 15 | top_border 16 | echo -e "| $(title_msg "~~~~~~~~~~~~~~ [ Backup Menu ] ~~~~~~~~~~~~~~") |" 17 | hr 18 | echo -e "| ${yellow}INFO: Backups are located in '~/kiauh-backups'${white} |" 19 | hr 20 | echo -e "| Klipper & API: | Touchscreen GUI: |" 21 | echo -e "| 1) [Klipper] | 7) [KlipperScreen] |" 22 | echo -e "| 2) [Moonraker] | |" 23 | echo -e "| 3) [Config Folder] | 3rd Party Webinterface: |" 24 | echo -e "| 4) [Moonraker Database] | 8) [OctoPrint] |" 25 | echo -e "| | |" 26 | echo -e "| Klipper Webinterface: | Other: |" 27 | echo -e "| 5) [Mainsail] | 9) [Telegram Bot] |" 28 | echo -e "| 6) [Fluidd] | 10) [OctoEverywhere] |" 29 | echo -e "| | 11) [Spoolman] |" 30 | back_footer 31 | } 32 | 33 | function backup_menu() { 34 | do_action "" "backup_ui" 35 | 36 | local action 37 | while true; do 38 | read -p "${cyan}####### Perform action:${white} " action 39 | case "${action}" in 40 | 1) 41 | do_action "backup_klipper" "backup_ui";; 42 | 2) 43 | do_action "backup_moonraker" "backup_ui";; 44 | 3) 45 | do_action "backup_config_dir" "backup_ui";; 46 | 4) 47 | do_action "backup_moonraker_database" "backup_ui";; 48 | 5) 49 | do_action "backup_mainsail" "backup_ui";; 50 | 6) 51 | do_action "backup_fluidd" "backup_ui";; 52 | 7) 53 | do_action "backup_klipperscreen" "backup_ui";; 54 | 8) 55 | do_action "backup_octoprint" "backup_ui";; 56 | 9) 57 | do_action "backup_telegram_bot" "backup_ui";; 58 | 10) 59 | do_action "backup_octoeverywhere" "backup_ui";; 60 | 11) 61 | do_action "backup_spoolman" "backup_ui";; 62 | B|b) 63 | clear; main_menu; break;; 64 | *) 65 | deny_action "backup_ui";; 66 | esac 67 | done 68 | backup_menu 69 | } 70 | -------------------------------------------------------------------------------- /scripts/ui/general_ui.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | #=======================================================================# 4 | # Copyright (C) 2020 - 2024 Dominik Willner <th33xitus@gmail.com> # 5 | # # 6 | # This file is part of KIAUH - Klipper Installation And Update Helper # 7 | # https://github.com/dw-0/kiauh # 8 | # # 9 | # This file may be distributed under the terms of the GNU GPLv3 license # 10 | #=======================================================================# 11 | 12 | set -e 13 | 14 | #ui total width = 57 chars 15 | function top_border() { 16 | echo -e "/=======================================================\\" 17 | } 18 | 19 | function bottom_border() { 20 | echo -e "\=======================================================/" 21 | } 22 | 23 | function blank_line() { 24 | echo -e "| |" 25 | } 26 | 27 | function hr() { 28 | echo -e "|-------------------------------------------------------|" 29 | } 30 | 31 | function quit_footer() { 32 | hr 33 | echo -e "| ${red}Q) Quit${white} |" 34 | bottom_border 35 | } 36 | 37 | function back_footer() { 38 | hr 39 | echo -e "| ${green}B) « Back${white} |" 40 | bottom_border 41 | } 42 | 43 | function back_help_footer() { 44 | hr 45 | echo -e "| ${green}B) « Back${white} | ${yellow}H) Help [?]${white} |" 46 | bottom_border 47 | } 48 | 49 | function print_header() { 50 | top_border 51 | echo -e "| $(title_msg "~~~~~~~~~~~~~~~~~ [ KIAUH ] ~~~~~~~~~~~~~~~~~") |" 52 | echo -e "| $(title_msg " Klipper Installation And Update Helper ") |" 53 | echo -e "| $(title_msg "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~") |" 54 | bottom_border 55 | } 56 | 57 | function do_action() { 58 | clear && print_header 59 | ### $1 is the action the user wants to fire 60 | $1 61 | # print_msg && clear_msg 62 | ### $2 is the menu the user usually gets directed back to after an action is completed 63 | $2 64 | } 65 | 66 | function deny_action() { 67 | clear && print_header 68 | print_error "Invalid command!" 69 | $1 70 | } 71 | -------------------------------------------------------------------------------- /scripts/ui/install_menu.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | #=======================================================================# 4 | # Copyright (C) 2020 - 2024 Dominik Willner <th33xitus@gmail.com> # 5 | # # 6 | # This file is part of KIAUH - Klipper Installation And Update Helper # 7 | # https://github.com/dw-0/kiauh # 8 | # # 9 | # This file may be distributed under the terms of the GNU GPLv3 license # 10 | #=======================================================================# 11 | 12 | set -e 13 | 14 | function install_ui() { 15 | top_border 16 | echo -e "| ${green}~~~~~~~~~~~ [ Installation Menu ] ~~~~~~~~~~~${white} |" 17 | hr 18 | echo -e "| You need this menu usually only for installing |" 19 | echo -e "| all necessary dependencies for the various |" 20 | echo -e "| functions on a completely fresh system. |" 21 | hr 22 | echo -e "| Firmware & API: | Other: |" 23 | echo -e "| 1) [Klipper] | 7) [PrettyGCode] |" 24 | echo -e "| 2) [Moonraker] | 8) [Telegram Bot] |" 25 | echo -e "| | 9) $(obico_install_title) |" 26 | echo -e "| Klipper Webinterface: | 10) [OctoEverywhere] |" 27 | echo -e "| 3) [Mainsail] | 11) [Mobileraker] |" 28 | echo -e "| 4) [Fluidd] | 12) [OctoApp for Klipper] |" 29 | echo -e "| | 13) [Spoolman] |" 30 | echo -e "| Touchscreen GUI: | |" 31 | echo -e "| 5) [KlipperScreen] | Webcam Streamer: |" 32 | echo -e "| | 14) [Crowsnest] |" 33 | echo -e "| 3rd Party Webinterface: | |" 34 | echo -e "| 6) [OctoPrint] | |" 35 | back_footer 36 | } 37 | 38 | function install_menu() { 39 | clear -x && sudo true && clear -x # (re)cache sudo credentials so password prompt doesn't bork ui 40 | print_header 41 | install_ui 42 | 43 | ### save all installed webinterface ports to the ini file 44 | fetch_webui_ports 45 | 46 | ### save all klipper multi-instance names to the ini file 47 | set_multi_instance_names 48 | 49 | local action 50 | while true; do 51 | read -p "${cyan}####### Perform action:${white} " action 52 | case "${action}" in 53 | 1) 54 | do_action "start_klipper_setup" "install_ui";; 55 | 2) 56 | do_action "moonraker_setup_dialog" "install_ui";; 57 | 3) 58 | do_action "install_mainsail" "install_ui";; 59 | 4) 60 | do_action "install_fluidd" "install_ui";; 61 | 5) 62 | do_action "install_klipperscreen" "install_ui";; 63 | 6) 64 | do_action "octoprint_setup_dialog" "install_ui";; 65 | 7) 66 | do_action "install_pgc_for_klipper" "install_ui";; 67 | 8) 68 | do_action "telegram_bot_setup_dialog" "install_ui";; 69 | 9) 70 | do_action "moonraker_obico_setup_dialog" "install_ui";; 71 | 10) 72 | do_action "octoeverywhere_setup_dialog" "install_ui";; 73 | 11) 74 | do_action "install_mobileraker" "install_ui";; 75 | 12) 76 | do_action "octoapp_setup_dialog" "install_ui";; 77 | 13) 78 | do_action "install_spoolman" "install_ui";; 79 | 14) 80 | do_action "install_crowsnest" "install_ui";; 81 | B|b) 82 | clear; main_menu; break;; 83 | *) 84 | deny_action "install_ui";; 85 | esac 86 | done 87 | install_menu 88 | } 89 | -------------------------------------------------------------------------------- /scripts/ui/remove_menu.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | #=======================================================================# 4 | # Copyright (C) 2020 - 2024 Dominik Willner <th33xitus@gmail.com> # 5 | # # 6 | # This file is part of KIAUH - Klipper Installation And Update Helper # 7 | # https://github.com/dw-0/kiauh # 8 | # # 9 | # This file may be distributed under the terms of the GNU GPLv3 license # 10 | #=======================================================================# 11 | 12 | set -e 13 | 14 | function remove_ui() { 15 | top_border 16 | echo -e "| ${red}~~~~~~~~~~~~~~ [ Remove Menu ] ~~~~~~~~~~~~~~${white} |" 17 | hr 18 | echo -e "| ${yellow}INFO: Configurations and/or any backups will be kept!${white} |" 19 | hr 20 | echo -e "| Firmware & API: | Webcam Streamer: |" 21 | echo -e "| 1) [Klipper] | 9) [Crowsnest] |" 22 | echo -e "| 2) [Moonraker] | 10) [MJPG-Streamer] |" 23 | echo -e "| | |" 24 | echo -e "| Klipper Webinterface: | Other: |" 25 | echo -e "| 3) [Mainsail] | 11) [PrettyGCode] |" 26 | echo -e "| 4) [Mainsail-Config] | 12) [Telegram Bot] |" 27 | echo -e "| 5) [Fluidd] | 13) [Obico for Klipper] |" 28 | echo -e "| 6) [Fluidd-Config] | 14) [OctoEverywhere] |" 29 | echo -e "| | 15) [Mobileraker] |" 30 | echo -e "| Touchscreen GUI: | 16) [NGINX] |" 31 | echo -e "| 7) [KlipperScreen] | 17) [OctoApp] |" 32 | echo -e "| | 18) [Spoolman] |" 33 | echo -e "| 3rd Party Webinterface: | |" 34 | echo -e "| 8) [OctoPrint] | |" 35 | back_footer 36 | } 37 | 38 | function remove_menu() { 39 | do_action "" "remove_ui" 40 | 41 | local action 42 | while true; do 43 | read -p "${cyan}####### Perform action:${white} " action 44 | case "${action}" in 45 | 1) 46 | do_action "remove_klipper" "remove_ui";; 47 | 2) 48 | do_action "remove_moonraker" "remove_ui";; 49 | 3) 50 | do_action "remove_mainsail" "remove_ui";; 51 | 4) 52 | do_action "remove_mainsail_config" "remove_ui";; 53 | 5) 54 | do_action "remove_fluidd" "remove_ui";; 55 | 6) 56 | do_action "remove_fluidd_config" "remove_ui";; 57 | 7) 58 | do_action "remove_klipperscreen" "remove_ui";; 59 | 8) 60 | do_action "remove_octoprint" "remove_ui";; 61 | 9) 62 | do_action "remove_crowsnest" "remove_ui";; 63 | 10) 64 | do_action "remove_mjpg-streamer" "remove_ui";; 65 | 11) 66 | do_action "remove_prettygcode" "remove_ui";; 67 | 12) 68 | do_action "remove_telegram_bot" "remove_ui";; 69 | 13) 70 | do_action "remove_moonraker_obico" "remove_ui";; 71 | 14) 72 | do_action "remove_octoeverywhere" "remove_ui";; 73 | 15) 74 | do_action "remove_mobileraker" "remove_ui";; 75 | 16) 76 | do_action "remove_nginx" "remove_ui";; 77 | 17) 78 | do_action "remove_octoapp" "remove_ui";; 79 | 18) 80 | do_action "remove_spoolman" "remove_ui";; 81 | B|b) 82 | clear; main_menu; break;; 83 | *) 84 | deny_action "remove_ui";; 85 | esac 86 | done 87 | remove_menu 88 | } 89 | --------------------------------------------------------------------------------