mirror of
https://github.com/dw-0/kiauh.git
synced 2025-12-14 11:04:29 +05:00
Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6cee0252ee | ||
|
|
aff63665de | ||
|
|
1ed1e0fc4c | ||
|
|
81ac102644 | ||
|
|
89b48168f4 | ||
|
|
195b7fa926 | ||
|
|
12919c7140 |
@@ -11,5 +11,5 @@ end_of_line = lf
|
||||
[*.py]
|
||||
max_line_length = 88
|
||||
|
||||
[*.{sh,yml,yaml,json,md}]
|
||||
[*.{sh,yml,yaml,json}]
|
||||
indent_size = 2
|
||||
31
.github/workflows/deploy-docs.yml
vendored
31
.github/workflows/deploy-docs.yml
vendored
@@ -1,31 +0,0 @@
|
||||
name: Deploy Documentation
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- docs
|
||||
permissions:
|
||||
contents: write
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Configure Git Credentials
|
||||
run: |
|
||||
git config user.name github-actions[bot]
|
||||
git config user.email 41898282+github-actions[bot]@users.noreply.github.com
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: 3.x
|
||||
- run: echo "cache_id=$(date --utc '+%V')" >> $GITHUB_ENV
|
||||
- uses: actions/cache@v4
|
||||
with:
|
||||
key: mkdocs-material-${{ env.cache_id }}
|
||||
path: .cache
|
||||
restore-keys: |
|
||||
mkdocs-material-
|
||||
- name: Install dependencies
|
||||
run: pip install -r requirements.txt
|
||||
- name: Build and deploy documentation
|
||||
run: mkdocs gh-deploy --force
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -10,3 +10,4 @@ __pycache__
|
||||
*.code-workspace
|
||||
*.iml
|
||||
kiauh.cfg
|
||||
klipper_repos.txt
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
FROM squidfunk/mkdocs-material:latest
|
||||
|
||||
# Install additional plugins required by our mkdocs configuration
|
||||
RUN pip install \
|
||||
mkdocs-git-revision-date-localized-plugin \
|
||||
mkdocstrings[python]
|
||||
@@ -1,8 +0,0 @@
|
||||
services:
|
||||
mkdocs:
|
||||
build: .
|
||||
ports:
|
||||
- "8000:8000"
|
||||
volumes:
|
||||
- ./:/docs
|
||||
command: serve --dev-addr=0.0.0.0:8000
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 22 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 49 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 58 KiB |
@@ -276,7 +276,7 @@ Each service gets its corresponding instance added to the service filename.
|
||||
|
||||
* The user can now choose to install Klipper as a systemd service.
|
||||
|
||||
* The Shell Command extension and `shell_command.py` got renamed to G-Code Shell Command extension and `gcode_shell_command.py`. In case the [pending PR](https://github.com/KevinOConnor/klipper/pull/2173) will be merged in the future, this was an early attempt to dodge possible incompatibilities. The [G-Code Shell Command docs](extensions/gcode-shell-command) has been updated accordingly.
|
||||
* The Shell Command extension and `shell_command.py` got renamed to G-Code Shell Command extension and `gcode_shell_command.py`. In case the [pending PR](https://github.com/KevinOConnor/klipper/pull/2173) will be merged in the future, this was an early attempt to dodge possible incompatibilities. The [G-Code Shell Command docs](gcode_shell_command.md) has been updated accordingly.
|
||||
|
||||
* The way how KIAUH interacts and writes to the users printer.cfg got changed. Usually KIAUH wrote everything directly into the printer.cfg. The way it will work from now on is, that a new file called `kiauh.cfg` will be created if there is something that needs to be written to the printer.cfg and everything gets written to `kiauh.cfg` instead. The only thing which then gets written to the users printer.cfg is `[include kiauh.cfg]`. This line will be located at the very top of the existing printer.cfg with a little comment as a note. The user can then decide to either keep the `kiauh.cfg` or take its content, places it into the printer.cfg directly and remove the `[include kiauh.cfg]`.
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
# Community Extensions
|
||||
@@ -1,23 +0,0 @@
|
||||
!!! tip "Important"
|
||||
This documentation is for KIAUH version 6 and still work in progress!
|
||||
|
||||
<h1 align="center">
|
||||
KIAUH - Klipper Installation And Update Helper
|
||||
</h1>
|
||||
|
||||
<p align="center">
|
||||
<img src="assets/logo-large.png" alt="KIAUH logo" width="400"/>
|
||||
</p>
|
||||
<p align="center" style="font-size: 1.2em; font-weight: bold;">
|
||||
A handy installation script that makes installing Klipper (and more) a breeze!
|
||||
</p>
|
||||
|
||||
|
||||
## Features
|
||||
|
||||
- Easy installation of Klipper and related components
|
||||
- Support for multiple instances
|
||||
- Extension system for additional functionality
|
||||
- Configuration management
|
||||
- And more!
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
# Installing KIAUH
|
||||
|
||||
In the following sections, you will be guided through the installation
|
||||
process step-by-step.
|
||||
|
||||
To use KIAUH, it is enough to download the script and run it on your
|
||||
Raspberry Pi or other compatible device. If you need to know how to
|
||||
set up a Raspberry Pi or if you are unsure whether your current setup
|
||||
is sufficient, please refer to the [Raspberry Pi Installation Guide](raspberry-pi-setup.md)
|
||||
and follow the steps therein. Afterwards, you can return to this guide to install KIAUH.
|
||||
|
||||
### Prerequisites
|
||||
Before you can download and run KIAUH, you need to ensure that ``git`` is
|
||||
installed on your system. Open a terminal and run the following command:
|
||||
|
||||
```bash
|
||||
sudo apt-get update && sudo apt-get install git -y
|
||||
```
|
||||
|
||||
### Downloading KIAUH
|
||||
After `git` was successfully installed, you can download KIAUH by
|
||||
cloning the repository from GitHub. It is recommended to clone it into
|
||||
your home directory. Run the following command in your terminal:
|
||||
```bash
|
||||
cd ~ && git clone https://github.com/dw-0/kiauh.git
|
||||
```
|
||||
|
||||
### Running KIAUH
|
||||
Once the repository is cloned, you can start KIAUH. Make sure you are in
|
||||
your home directory and execute the script by running the following
|
||||
command:
|
||||
```bash
|
||||
./kiauh/kiauh.sh
|
||||
```
|
||||
|
||||
After executing the command, you will be presented with the KIAUH menu,
|
||||
which allows you to install and manage various 3D printing software.
|
||||
For more information on how to use KIAUH, please refer to the
|
||||
[Usage Guide](usage.md).
|
||||
@@ -1,49 +0,0 @@
|
||||
# Raspberry Pi Setup
|
||||
|
||||
This guide will help you set up a Raspberry Pi for running Klipper and other,
|
||||
Klipper related 3D printing software. In case you are using a different single-board
|
||||
computer (SBC), please refer to the manufacturer's instructions for installing
|
||||
a compatible version of Linux on your device.
|
||||
|
||||
It is assumed that you have at least a Raspberry Pi 3 or newer, along with a
|
||||
microSD card (at least 8GB, preferably 16GB or more) and a power supply.
|
||||
Additionally, you will need a computer with an SD card reader to prepare
|
||||
the microSD card.
|
||||
|
||||
KIAUH requires a Linux operating system that has already been flashed to your
|
||||
Raspberry Pi's (or other SBC's) microSD card. As a result, you must ensure that you
|
||||
already have a functional Linux system on hand before you can proceed with
|
||||
installing KIAUH. `Raspberry Pi OS Lite` (either 32bit or 64bit) is a recommended Linux image
|
||||
if you are using a Raspberry Pi.
|
||||
|
||||
---
|
||||
|
||||
To flash `Raspberry Pi OS Lite` to your microSD card using the official [Raspberry Pi Imager](https://www.raspberrypi.com/software/),
|
||||
follow the steps below. If you encounter any issues or need further assistance, please refer to the [official Raspberry Pi documentation](https://www.raspberrypi.com/documentation/computers/getting-started.html).
|
||||
|
||||
1. Open the Raspberry Pi Imager application on your computer.
|
||||
2. Click on `Choose OS` and select `Raspberry Pi OS (other)`.
|
||||

|
||||
3. Choose `Raspberry Pi OS Lite (32bit)` (or 64bit if desired).
|
||||

|
||||
4. Insert the microSD card into your computer's SD card reader.
|
||||
5. In the main menu of the Imager, select the correct microSD card.
|
||||
6. Click the gear icon at the bottom left of the main menu to open advanced options.
|
||||
7. Enable SSH and enter your Wi-Fi credentials.
|
||||
|
||||
!!! info
|
||||
Wi-Fi is only necessary if you want to connect to your Raspberry Pi over a wireless network. If you plan to use a wired Ethernet connection, you can skip this step. SSH is required for remote access to your Raspberry Pi, so make sure to enable it.
|
||||
|
||||
8. Click `Save` to close the advanced options menu.
|
||||
9. Click `Write` to start flashing the image to the microSD card.
|
||||
|
||||
!!! warning
|
||||
All data on the microSD card will be overwritten!
|
||||
|
||||
10. Once the flashing process is complete, safely eject the microSD card from your computer.
|
||||
11. Insert the microSD card into your Raspberry Pi.
|
||||
12. Connect your Raspberry Pi to a power source to boot it up.
|
||||
13. Wait for a few minutes to allow the Raspberry Pi to complete its initial setup.
|
||||
14. You can now connect to your Raspberry Pi via SSH using the IP address assigned by your router. The default username is `pi` and the default password is `raspberry`.
|
||||
|
||||
If you successfully connected to your Raspberry Pi via SSH, you can proceed to install KIAUH by following the instructions in the [Installation Guide](installation.md).
|
||||
@@ -9,7 +9,6 @@
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from core.backup_manager import BACKUP_ROOT_DIR
|
||||
from core.constants import SYSTEMD
|
||||
|
||||
# repo
|
||||
@@ -20,7 +19,6 @@ CROWSNEST_SERVICE_NAME = "crowsnest.service"
|
||||
|
||||
# directories
|
||||
CROWSNEST_DIR = Path.home().joinpath("crowsnest")
|
||||
CROWSNEST_BACKUP_DIR = BACKUP_ROOT_DIR.joinpath("crowsnest-backups")
|
||||
|
||||
# files
|
||||
CROWSNEST_MULTI_CONFIG = CROWSNEST_DIR.joinpath("tools/.config")
|
||||
|
||||
@@ -15,7 +15,6 @@ from subprocess import CalledProcessError, run
|
||||
from typing import List
|
||||
|
||||
from components.crowsnest import (
|
||||
CROWSNEST_BACKUP_DIR,
|
||||
CROWSNEST_BIN_FILE,
|
||||
CROWSNEST_DIR,
|
||||
CROWSNEST_INSTALL_SCRIPT,
|
||||
@@ -26,8 +25,8 @@ from components.crowsnest import (
|
||||
CROWSNEST_SERVICE_NAME,
|
||||
)
|
||||
from components.klipper.klipper import Klipper
|
||||
from core.backup_manager.backup_manager import BackupManager
|
||||
from core.logger import DialogType, Logger
|
||||
from core.services.backup_service import BackupService
|
||||
from core.settings.kiauh_settings import KiauhSettings
|
||||
from core.types.component_status import ComponentStatus
|
||||
from utils.common import (
|
||||
@@ -127,11 +126,11 @@ def update_crowsnest() -> None:
|
||||
|
||||
settings = KiauhSettings()
|
||||
if settings.kiauh.backup_before_update:
|
||||
bm = BackupManager()
|
||||
bm.backup_directory(
|
||||
CROWSNEST_DIR.name,
|
||||
source=CROWSNEST_DIR,
|
||||
target=CROWSNEST_BACKUP_DIR,
|
||||
svc = BackupService()
|
||||
svc.backup_directory(
|
||||
source_path=CROWSNEST_DIR,
|
||||
target_path="crowsnest",
|
||||
backup_name="crowsnest",
|
||||
)
|
||||
|
||||
git_pull_wrapper(CROWSNEST_DIR)
|
||||
|
||||
@@ -9,8 +9,6 @@
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from core.backup_manager import BACKUP_ROOT_DIR
|
||||
|
||||
MODULE_PATH = Path(__file__).resolve().parent
|
||||
|
||||
KLIPPER_REPO_URL = "https://github.com/Klipper3d/klipper.git"
|
||||
@@ -27,7 +25,6 @@ KLIPPER_SERVICE_NAME = "klipper.service"
|
||||
KLIPPER_DIR = Path.home().joinpath("klipper")
|
||||
KLIPPER_KCONFIGS_DIR = Path.home().joinpath("klipper-kconfigs")
|
||||
KLIPPER_ENV_DIR = Path.home().joinpath("klippy-env")
|
||||
KLIPPER_BACKUP_DIR = BACKUP_ROOT_DIR.joinpath("klipper-backups")
|
||||
|
||||
# files
|
||||
KLIPPER_REQ_FILE = KLIPPER_DIR.joinpath("scripts/klippy-requirements.txt")
|
||||
|
||||
@@ -16,7 +16,6 @@ from subprocess import CalledProcessError, run
|
||||
from typing import Dict, List
|
||||
|
||||
from components.klipper import (
|
||||
KLIPPER_BACKUP_DIR,
|
||||
KLIPPER_DIR,
|
||||
KLIPPER_ENV_DIR,
|
||||
KLIPPER_INSTALL_SCRIPT,
|
||||
@@ -31,10 +30,10 @@ from components.webui_client.base_data import BaseWebClient
|
||||
from components.webui_client.client_config.client_config_setup import (
|
||||
create_client_config_symlink,
|
||||
)
|
||||
from core.backup_manager.backup_manager import BackupManager
|
||||
from core.constants import CURRENT_USER
|
||||
from core.instance_manager.base_instance import SUFFIX_BLACKLIST
|
||||
from core.logger import DialogType, Logger
|
||||
from core.services.backup_service import BackupService
|
||||
from core.submodules.simple_config_parser.src.simple_config_parser.simple_config_parser import (
|
||||
SimpleConfigParser,
|
||||
)
|
||||
@@ -198,9 +197,17 @@ def create_example_printer_cfg(
|
||||
|
||||
|
||||
def backup_klipper_dir() -> None:
|
||||
bm = BackupManager()
|
||||
bm.backup_directory("klipper", source=KLIPPER_DIR, target=KLIPPER_BACKUP_DIR)
|
||||
bm.backup_directory("klippy-env", source=KLIPPER_ENV_DIR, target=KLIPPER_BACKUP_DIR)
|
||||
svc = BackupService()
|
||||
svc.backup_directory(
|
||||
source_path=KLIPPER_DIR,
|
||||
backup_name="klipper",
|
||||
target_path="klipper",
|
||||
)
|
||||
svc.backup_directory(
|
||||
source_path=KLIPPER_ENV_DIR,
|
||||
backup_name="klippy-env",
|
||||
target_path="klipper",
|
||||
)
|
||||
|
||||
|
||||
def install_klipper_packages() -> None:
|
||||
|
||||
0
kiauh/components/klipper/services/__init__.py
Normal file
0
kiauh/components/klipper/services/__init__.py
Normal file
@@ -8,7 +8,6 @@
|
||||
# ======================================================================= #
|
||||
from pathlib import Path
|
||||
|
||||
from core.backup_manager import BACKUP_ROOT_DIR
|
||||
from core.constants import SYSTEMD
|
||||
|
||||
# repo
|
||||
@@ -22,7 +21,6 @@ KLIPPERSCREEN_LOG_NAME = "KlipperScreen.log"
|
||||
# directories
|
||||
KLIPPERSCREEN_DIR = Path.home().joinpath("KlipperScreen")
|
||||
KLIPPERSCREEN_ENV_DIR = Path.home().joinpath(".KlipperScreen-env")
|
||||
KLIPPERSCREEN_BACKUP_DIR = BACKUP_ROOT_DIR.joinpath("klipperscreen-backups")
|
||||
|
||||
# files
|
||||
KLIPPERSCREEN_REQ_FILE = KLIPPERSCREEN_DIR.joinpath(
|
||||
|
||||
@@ -13,7 +13,6 @@ from typing import List
|
||||
|
||||
from components.klipper.klipper import Klipper
|
||||
from components.klipperscreen import (
|
||||
KLIPPERSCREEN_BACKUP_DIR,
|
||||
KLIPPERSCREEN_DIR,
|
||||
KLIPPERSCREEN_ENV_DIR,
|
||||
KLIPPERSCREEN_INSTALL_SCRIPT,
|
||||
@@ -25,10 +24,10 @@ from components.klipperscreen import (
|
||||
KLIPPERSCREEN_UPDATER_SECTION_NAME,
|
||||
)
|
||||
from components.moonraker.moonraker import Moonraker
|
||||
from core.backup_manager.backup_manager import BackupManager
|
||||
from core.constants import SYSTEMD
|
||||
from core.instance_manager.instance_manager import InstanceManager
|
||||
from core.logger import DialogType, Logger
|
||||
from core.services.backup_service import BackupService
|
||||
from core.settings.kiauh_settings import KiauhSettings
|
||||
from core.types.component_status import ComponentStatus
|
||||
from utils.common import (
|
||||
@@ -193,14 +192,14 @@ def remove_klipperscreen() -> None:
|
||||
|
||||
|
||||
def backup_klipperscreen_dir() -> None:
|
||||
bm = BackupManager()
|
||||
bm.backup_directory(
|
||||
KLIPPERSCREEN_DIR.name,
|
||||
source=KLIPPERSCREEN_DIR,
|
||||
target=KLIPPERSCREEN_BACKUP_DIR,
|
||||
svc = BackupService()
|
||||
svc.backup_directory(
|
||||
source_path=KLIPPERSCREEN_DIR,
|
||||
backup_name="KlipperScreen",
|
||||
target_path="KlipperScreen",
|
||||
)
|
||||
bm.backup_directory(
|
||||
KLIPPERSCREEN_ENV_DIR.name,
|
||||
source=KLIPPERSCREEN_ENV_DIR,
|
||||
target=KLIPPERSCREEN_BACKUP_DIR,
|
||||
svc.backup_directory(
|
||||
source_path=KLIPPERSCREEN_ENV_DIR,
|
||||
backup_name="KlipperScreen-env",
|
||||
target_path="KlipperScreen",
|
||||
)
|
||||
|
||||
@@ -9,8 +9,6 @@
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from core.backup_manager import BACKUP_ROOT_DIR
|
||||
|
||||
MODULE_PATH = Path(__file__).resolve().parent
|
||||
|
||||
MOONRAKER_REPO_URL = "https://github.com/Arksine/moonraker.git"
|
||||
@@ -25,8 +23,6 @@ MOONRAKER_ENV_FILE_NAME = "moonraker.env"
|
||||
# directories
|
||||
MOONRAKER_DIR = Path.home().joinpath("moonraker")
|
||||
MOONRAKER_ENV_DIR = Path.home().joinpath("moonraker-env")
|
||||
MOONRAKER_BACKUP_DIR = BACKUP_ROOT_DIR.joinpath("moonraker-backups")
|
||||
MOONRAKER_DB_BACKUP_DIR = BACKUP_ROOT_DIR.joinpath("moonraker-db-backups")
|
||||
|
||||
# files
|
||||
MOONRAKER_INSTALL_SCRIPT = MOONRAKER_DIR.joinpath("scripts/install-moonraker.sh")
|
||||
|
||||
0
kiauh/components/moonraker/menus/__init__.py
Normal file
0
kiauh/components/moonraker/menus/__init__.py
Normal file
@@ -61,6 +61,9 @@ class SysDepsParser:
|
||||
version = distro_info.get("distro_version")
|
||||
if version:
|
||||
self.distro_version = _convert_version(version)
|
||||
self.vendor: str = ""
|
||||
if pathlib.Path("/etc/rpi-issue").is_file():
|
||||
self.vendor = "raspberry-pi"
|
||||
|
||||
def _parse_spec(self, full_spec: str) -> str | None:
|
||||
parts = full_spec.split(";", maxsplit=1)
|
||||
@@ -109,6 +112,9 @@ class SysDepsParser:
|
||||
elif req_var == "distro_id":
|
||||
left_op: str | Tuple[int | str, ...] = self.distro_id
|
||||
right_op = dep_parts[2].strip().strip("\"'")
|
||||
elif req_var == "vendor":
|
||||
left_op = self.vendor
|
||||
right_op = dep_parts[2].strip().strip("\"'")
|
||||
elif req_var == "distro_version":
|
||||
if not self.distro_version:
|
||||
logging.info(
|
||||
|
||||
@@ -14,8 +14,6 @@ from typing import Dict, List, Optional
|
||||
|
||||
from components.moonraker import (
|
||||
MODULE_PATH,
|
||||
MOONRAKER_BACKUP_DIR,
|
||||
MOONRAKER_DB_BACKUP_DIR,
|
||||
MOONRAKER_DEFAULT_PORT,
|
||||
MOONRAKER_DEPS_JSON_FILE,
|
||||
MOONRAKER_DIR,
|
||||
@@ -25,8 +23,8 @@ from components.moonraker import (
|
||||
from components.moonraker.moonraker import Moonraker
|
||||
from components.moonraker.utils.sysdeps_parser import SysDepsParser
|
||||
from components.webui_client.base_data import BaseWebClient
|
||||
from core.backup_manager.backup_manager import BackupManager
|
||||
from core.logger import Logger
|
||||
from core.services.backup_service import BackupService
|
||||
from core.submodules.simple_config_parser.src.simple_config_parser.simple_config_parser import (
|
||||
SimpleConfigParser,
|
||||
)
|
||||
@@ -168,21 +166,31 @@ def create_example_moonraker_conf(
|
||||
|
||||
|
||||
def backup_moonraker_dir() -> None:
|
||||
bm = BackupManager()
|
||||
bm.backup_directory("moonraker", source=MOONRAKER_DIR, target=MOONRAKER_BACKUP_DIR)
|
||||
bm.backup_directory(
|
||||
"moonraker-env", source=MOONRAKER_ENV_DIR, target=MOONRAKER_BACKUP_DIR
|
||||
svc = BackupService()
|
||||
svc.backup_directory(
|
||||
source_path=MOONRAKER_DIR, backup_name="moonraker", target_path="moonraker"
|
||||
)
|
||||
svc.backup_directory(
|
||||
source_path=MOONRAKER_ENV_DIR,
|
||||
backup_name="moonraker-env",
|
||||
target_path="moonraker",
|
||||
)
|
||||
|
||||
|
||||
def backup_moonraker_db_dir() -> None:
|
||||
instances: List[Moonraker] = get_instances(Moonraker)
|
||||
bm = BackupManager()
|
||||
svc = BackupService()
|
||||
|
||||
if not instances:
|
||||
Logger.print_info("Unable to find directory to backup!")
|
||||
Logger.print_info("Are there no Moonraker instances installed?")
|
||||
return
|
||||
|
||||
for instance in instances:
|
||||
name = f"database-{instance.data_dir.name}"
|
||||
bm.backup_directory(
|
||||
name, source=instance.db_dir, target=MOONRAKER_DB_BACKUP_DIR
|
||||
svc.backup_directory(
|
||||
source_path=instance.db_dir,
|
||||
target_path=f"{instance.data_dir.name}",
|
||||
backup_name="database",
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -34,7 +34,6 @@ class BaseWebClient(ABC):
|
||||
display_name: str
|
||||
client_dir: Path
|
||||
config_file: Path
|
||||
backup_dir: Path
|
||||
repo_path: str
|
||||
download_url: str
|
||||
nginx_config: Path
|
||||
@@ -52,6 +51,5 @@ class BaseWebClientConfig(ABC):
|
||||
display_name: str
|
||||
config_filename: str
|
||||
config_dir: Path
|
||||
backup_dir: Path
|
||||
repo_url: str
|
||||
config_section: str
|
||||
|
||||
@@ -16,9 +16,9 @@ from components.webui_client.base_data import (
|
||||
from components.webui_client.client_config.client_config_remove import (
|
||||
run_client_config_removal,
|
||||
)
|
||||
from core.backup_manager.backup_manager import BackupManager
|
||||
from core.constants import NGINX_SITES_AVAILABLE, NGINX_SITES_ENABLED
|
||||
from core.logger import Logger
|
||||
from core.services.backup_service import BackupService
|
||||
from core.services.message_service import Message
|
||||
from core.types.color import Color
|
||||
from utils.config_utils import remove_config_section
|
||||
@@ -43,8 +43,19 @@ def run_client_removal(
|
||||
kl_instances: List[Klipper] = get_instances(Klipper)
|
||||
|
||||
if backup_config:
|
||||
bm = BackupManager()
|
||||
if bm.backup_file(client.config_file):
|
||||
version = ""
|
||||
src = client.client_dir
|
||||
if src.joinpath(".version").exists():
|
||||
with open(src.joinpath(".version"), "r") as v:
|
||||
version = v.readlines()[0]
|
||||
|
||||
svc = BackupService()
|
||||
target_path = svc.backup_root.joinpath(f"{client.client_dir.name}_{version}")
|
||||
success = svc.backup_file(
|
||||
source_path=client.config_file,
|
||||
target_path=target_path,
|
||||
)
|
||||
if success:
|
||||
completion_msg.text.append(f"● {client.config_file.name} backup created")
|
||||
|
||||
if remove_client:
|
||||
|
||||
@@ -24,13 +24,13 @@ from components.webui_client.base_data import (
|
||||
from components.webui_client.client_dialogs import print_client_port_select_dialog
|
||||
from components.webui_client.fluidd_data import FluiddData
|
||||
from components.webui_client.mainsail_data import MainsailData
|
||||
from core.backup_manager.backup_manager import BackupManager
|
||||
from core.constants import (
|
||||
NGINX_CONFD,
|
||||
NGINX_SITES_AVAILABLE,
|
||||
NGINX_SITES_ENABLED,
|
||||
)
|
||||
from core.logger import Logger
|
||||
from core.services.backup_service import BackupService
|
||||
from core.settings.kiauh_settings import KiauhSettings, WebUiSettings
|
||||
from core.submodules.simple_config_parser.src.simple_config_parser.simple_config_parser import (
|
||||
SimpleConfigParser,
|
||||
@@ -118,7 +118,7 @@ def enable_mainsail_remotemode() -> None:
|
||||
c_json = MainsailData().client_dir.joinpath("config.json")
|
||||
with open(c_json, "r") as f:
|
||||
config_data = json.load(f)
|
||||
|
||||
|
||||
if config_data["instancesDB"] == "browser" or config_data["instancesDB"] == "json":
|
||||
Logger.print_info("Remote mode already configured. Skipped ...")
|
||||
return
|
||||
@@ -175,26 +175,39 @@ def get_remote_client_version(client: BaseWebClient) -> str | None:
|
||||
|
||||
|
||||
def backup_client_data(client: BaseWebClient) -> None:
|
||||
name = client.name
|
||||
version = ""
|
||||
src = client.client_dir
|
||||
dest = client.backup_dir
|
||||
if src.joinpath(".version").exists():
|
||||
with open(src.joinpath(".version"), "r") as v:
|
||||
version = v.readlines()[0]
|
||||
|
||||
with open(src.joinpath(".version"), "r") as v:
|
||||
version = v.readlines()[0]
|
||||
|
||||
bm = BackupManager()
|
||||
bm.backup_directory(f"{name}-{version}", src, dest)
|
||||
bm.backup_file(client.config_file, dest)
|
||||
bm.backup_file(NGINX_SITES_AVAILABLE.joinpath(name), dest)
|
||||
svc = BackupService()
|
||||
target_path = svc.backup_root.joinpath(f"{client.client_dir.name}_{version}")
|
||||
svc.backup_directory(
|
||||
source_path=client.client_dir,
|
||||
target_path=target_path,
|
||||
backup_name=client.name,
|
||||
)
|
||||
svc.backup_file(
|
||||
source_path=client.config_file,
|
||||
target_path=target_path,
|
||||
)
|
||||
|
||||
|
||||
def backup_client_config_data(client: BaseWebClient) -> None:
|
||||
client_config = client.client_config
|
||||
name = client_config.name
|
||||
source = client_config.config_dir
|
||||
target = client_config.backup_dir
|
||||
bm = BackupManager()
|
||||
bm.backup_directory(name, source, target)
|
||||
version = ""
|
||||
src = client.client_dir
|
||||
if src.joinpath(".version").exists():
|
||||
with open(src.joinpath(".version"), "r") as v:
|
||||
version = v.readlines()[0]
|
||||
|
||||
svc = BackupService()
|
||||
target_path = svc.backup_root.joinpath(f"{client.client_dir.name}_{version}")
|
||||
svc.backup_directory(
|
||||
source_path=client.client_config.config_dir,
|
||||
target_path=target_path,
|
||||
backup_name=client.client_config.name,
|
||||
)
|
||||
|
||||
|
||||
def get_existing_clients() -> List[BaseWebClient]:
|
||||
|
||||
@@ -18,7 +18,6 @@ from components.webui_client.base_data import (
|
||||
WebClientConfigType,
|
||||
WebClientType,
|
||||
)
|
||||
from core.backup_manager import BACKUP_ROOT_DIR
|
||||
from core.constants import NGINX_SITES_AVAILABLE
|
||||
|
||||
|
||||
@@ -30,7 +29,6 @@ class FluiddConfigWeb(BaseWebClientConfig):
|
||||
config_dir: Path = Path.home().joinpath("fluidd-config")
|
||||
config_filename: str = "fluidd.cfg"
|
||||
config_section: str = f"include {config_filename}"
|
||||
backup_dir: Path = BACKUP_ROOT_DIR.joinpath("fluidd-config-backups")
|
||||
repo_url: str = "https://github.com/fluidd-core/fluidd-config.git"
|
||||
|
||||
|
||||
@@ -43,7 +41,6 @@ class FluiddData(BaseWebClient):
|
||||
display_name: str = name.capitalize()
|
||||
client_dir: Path = Path.home().joinpath("fluidd")
|
||||
config_file: Path = client_dir.joinpath("config.json")
|
||||
backup_dir: Path = BACKUP_ROOT_DIR.joinpath("fluidd-backups")
|
||||
repo_path: str = "fluidd-core/fluidd"
|
||||
nginx_config: Path = NGINX_SITES_AVAILABLE.joinpath("fluidd")
|
||||
nginx_access_log: Path = Path("/var/log/nginx/fluidd-access.log")
|
||||
|
||||
@@ -18,7 +18,6 @@ from components.webui_client.base_data import (
|
||||
WebClientConfigType,
|
||||
WebClientType,
|
||||
)
|
||||
from core.backup_manager import BACKUP_ROOT_DIR
|
||||
from core.constants import NGINX_SITES_AVAILABLE
|
||||
|
||||
|
||||
@@ -30,7 +29,6 @@ class MainsailConfigWeb(BaseWebClientConfig):
|
||||
config_dir: Path = Path.home().joinpath("mainsail-config")
|
||||
config_filename: str = "mainsail.cfg"
|
||||
config_section: str = f"include {config_filename}"
|
||||
backup_dir: Path = BACKUP_ROOT_DIR.joinpath("mainsail-config-backups")
|
||||
repo_url: str = "https://github.com/mainsail-crew/mainsail-config.git"
|
||||
|
||||
|
||||
@@ -43,7 +41,6 @@ class MainsailData(BaseWebClient):
|
||||
display_name: str = name.capitalize()
|
||||
client_dir: Path = Path.home().joinpath("mainsail")
|
||||
config_file: Path = client_dir.joinpath("config.json")
|
||||
backup_dir: Path = BACKUP_ROOT_DIR.joinpath("mainsail-backups")
|
||||
repo_path: str = "mainsail-crew/mainsail"
|
||||
nginx_config: Path = NGINX_SITES_AVAILABLE.joinpath("mainsail")
|
||||
nginx_access_log: Path = Path("/var/log/nginx/mainsail-access.log")
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
# ======================================================================= #
|
||||
# Copyright (C) 2020 - 2025 Dominik Willner <th33xitus@gmail.com> #
|
||||
# #
|
||||
# This file is part of KIAUH - Klipper Installation And Update Helper #
|
||||
# https://github.com/dw-0/kiauh #
|
||||
# #
|
||||
# This file may be distributed under the terms of the GNU GPLv3 license #
|
||||
# ======================================================================= #
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
BACKUP_ROOT_DIR = Path.home().joinpath("kiauh-backups")
|
||||
@@ -1,108 +0,0 @@
|
||||
# ======================================================================= #
|
||||
# Copyright (C) 2020 - 2025 Dominik Willner <th33xitus@gmail.com> #
|
||||
# #
|
||||
# This file is part of KIAUH - Klipper Installation And Update Helper #
|
||||
# https://github.com/dw-0/kiauh #
|
||||
# #
|
||||
# This file may be distributed under the terms of the GNU GPLv3 license #
|
||||
# ======================================================================= #
|
||||
from __future__ import annotations
|
||||
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
from typing import List
|
||||
|
||||
from core.backup_manager import BACKUP_ROOT_DIR
|
||||
from core.logger import Logger
|
||||
from utils.common import get_current_date
|
||||
|
||||
|
||||
class BackupManagerException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
# noinspection PyUnusedLocal
|
||||
# noinspection PyMethodMayBeStatic
|
||||
class BackupManager:
|
||||
def __init__(self, backup_root_dir: Path = BACKUP_ROOT_DIR):
|
||||
self._backup_root_dir: Path = backup_root_dir
|
||||
self._ignore_folders: List[str] = []
|
||||
|
||||
@property
|
||||
def backup_root_dir(self) -> Path:
|
||||
return self._backup_root_dir
|
||||
|
||||
@backup_root_dir.setter
|
||||
def backup_root_dir(self, value: Path):
|
||||
self._backup_root_dir = value
|
||||
|
||||
@property
|
||||
def ignore_folders(self) -> List[str]:
|
||||
return self._ignore_folders
|
||||
|
||||
@ignore_folders.setter
|
||||
def ignore_folders(self, value: List[str]):
|
||||
self._ignore_folders = value
|
||||
|
||||
def backup_file(
|
||||
self, file: Path, target: Path | None = None, custom_filename=None
|
||||
) -> bool:
|
||||
Logger.print_status(f"Creating backup of {file} ...")
|
||||
|
||||
if not file.exists():
|
||||
Logger.print_info("File does not exist! Skipping ...")
|
||||
return False
|
||||
|
||||
target = self.backup_root_dir if target is None else target
|
||||
|
||||
if Path(file).is_file():
|
||||
date = get_current_date().get("date")
|
||||
time = get_current_date().get("time")
|
||||
filename = f"{file.stem}-{date}-{time}{file.suffix}"
|
||||
filename = custom_filename if custom_filename is not None else filename
|
||||
try:
|
||||
Path(target).mkdir(exist_ok=True)
|
||||
shutil.copyfile(file, target.joinpath(filename))
|
||||
Logger.print_ok("Backup successful!")
|
||||
return True
|
||||
except OSError as e:
|
||||
Logger.print_error(f"Unable to backup '{file}':\n{e}")
|
||||
return False
|
||||
else:
|
||||
Logger.print_info(f"File '{file}' not found ...")
|
||||
return False
|
||||
|
||||
def backup_directory(
|
||||
self, name: str, source: Path, target: Path | None = None
|
||||
) -> Path | None:
|
||||
Logger.print_status(f"Creating backup of {name} in {target} ...")
|
||||
|
||||
if source is None or not Path(source).exists():
|
||||
Logger.print_info("Source directory does not exist! Skipping ...")
|
||||
return None
|
||||
|
||||
target = self.backup_root_dir if target is None else target
|
||||
try:
|
||||
date = get_current_date().get("date")
|
||||
time = get_current_date().get("time")
|
||||
backup_target = target.joinpath(f"{name.lower()}-{date}-{time}")
|
||||
shutil.copytree(
|
||||
source,
|
||||
backup_target,
|
||||
ignore=self.ignore_folders_func,
|
||||
ignore_dangling_symlinks=True,
|
||||
)
|
||||
Logger.print_ok("Backup successful!")
|
||||
|
||||
return backup_target
|
||||
|
||||
except OSError as e:
|
||||
Logger.print_error(f"Unable to backup directory '{source}':\n{e}")
|
||||
raise BackupManagerException(f"Unable to backup directory '{source}':\n{e}")
|
||||
|
||||
def ignore_folders_func(self, dirpath, filenames) -> List[str]:
|
||||
return (
|
||||
[f for f in filenames if f in self._ignore_folders]
|
||||
if self._ignore_folders
|
||||
else []
|
||||
)
|
||||
@@ -11,8 +11,6 @@ import os
|
||||
import pwd
|
||||
from pathlib import Path
|
||||
|
||||
from core.backup_manager import BACKUP_ROOT_DIR
|
||||
|
||||
# global dependencies
|
||||
GLOBAL_DEPS = ["git", "wget", "curl", "unzip", "dfu-util", "python3-virtualenv"]
|
||||
|
||||
@@ -24,7 +22,6 @@ CURRENT_USER = pwd.getpwuid(os.getuid())[0]
|
||||
|
||||
# dirs
|
||||
SYSTEMD = Path("/etc/systemd/system")
|
||||
PRINTER_DATA_BACKUP_DIR = BACKUP_ROOT_DIR.joinpath("printer-data-backups")
|
||||
NGINX_SITES_AVAILABLE = Path("/etc/nginx/sites-available")
|
||||
NGINX_SITES_ENABLED = Path("/etc/nginx/sites-enabled")
|
||||
NGINX_CONFD = Path("/etc/nginx/conf.d")
|
||||
|
||||
@@ -16,8 +16,9 @@ from typing import List
|
||||
|
||||
from utils.fs_utils import get_data_dir
|
||||
|
||||
SUFFIX_BLACKLIST: List[str] = ["None", "mcu", "obico", "bambu", "companion"]
|
||||
|
||||
# suffixes that are not allowed to be used for instances
|
||||
# because they would cause conflicts with other components or are reserved
|
||||
SUFFIX_BLACKLIST: List[str] = ["None", "mcu", "obico", "bambu", "companion", "hmi"]
|
||||
|
||||
@dataclass(repr=True)
|
||||
class BaseInstance:
|
||||
|
||||
111
kiauh/core/services/backup_service.py
Normal file
111
kiauh/core/services/backup_service.py
Normal file
@@ -0,0 +1,111 @@
|
||||
# ======================================================================= #
|
||||
# Copyright (C) 2020 - 2025 Dominik Willner <th33xitus@gmail.com> #
|
||||
# #
|
||||
# This file is part of KIAUH - Klipper Installation And Update Helper #
|
||||
# https://github.com/dw-0/kiauh #
|
||||
# #
|
||||
# This file may be distributed under the terms of the GNU GPLv3 license #
|
||||
# ======================================================================= #
|
||||
from __future__ import annotations
|
||||
|
||||
import shutil
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
from core.logger import Logger
|
||||
|
||||
|
||||
class BackupService:
|
||||
def __init__(self):
|
||||
self._backup_root = Path.home().joinpath("kiauh_backups")
|
||||
|
||||
@property
|
||||
def backup_root(self) -> Path:
|
||||
return self._backup_root
|
||||
|
||||
def backup_file(
|
||||
self,
|
||||
source_path: Path,
|
||||
target_path: Optional[Path | str] = None,
|
||||
target_name: Optional[str] = None,
|
||||
) -> bool:
|
||||
source_path = Path(source_path)
|
||||
|
||||
Logger.print_status(f"Creating backup of {source_path} ...")
|
||||
|
||||
if not source_path.exists():
|
||||
Logger.print_info(
|
||||
f"File '{source_path}' does not exist! Skipping backup..."
|
||||
)
|
||||
return False
|
||||
|
||||
if not source_path.is_file():
|
||||
Logger.print_info(f"'{source_path}' is not a file! Skipping backup...")
|
||||
return False
|
||||
|
||||
try:
|
||||
self._backup_root.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
timestamp = datetime.now().strftime("%Y%m%d-%H%M%S")
|
||||
filename = (
|
||||
target_name or f"{source_path.stem}_{timestamp}{source_path.suffix}"
|
||||
)
|
||||
if target_path is not None:
|
||||
backup_path = self._backup_root.joinpath(target_path, filename)
|
||||
else:
|
||||
backup_path = self._backup_root.joinpath(filename)
|
||||
|
||||
backup_path.mkdir(parents=True, exist_ok=True)
|
||||
shutil.copy2(source_path, backup_path)
|
||||
|
||||
Logger.print_ok(
|
||||
f"Successfully backed up '{source_path}' to '{backup_path}'"
|
||||
)
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
Logger.print_error(f"Failed to backup '{source_path}': {e}")
|
||||
return False
|
||||
|
||||
def backup_directory(
|
||||
self,
|
||||
source_path: Path,
|
||||
backup_name: str,
|
||||
target_path: Optional[Path | str] = None,
|
||||
) -> Optional[Path]:
|
||||
source_path = Path(source_path)
|
||||
|
||||
Logger.print_status(f"Creating backup of {source_path} ...")
|
||||
|
||||
if not source_path.exists():
|
||||
Logger.print_info(
|
||||
f"Directory '{source_path}' does not exist! Skipping backup..."
|
||||
)
|
||||
return None
|
||||
|
||||
if not source_path.is_dir():
|
||||
Logger.print_info(f"'{source_path}' is not a directory! Skipping backup...")
|
||||
return None
|
||||
|
||||
try:
|
||||
self._backup_root.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
timestamp = datetime.now().strftime("%Y%m%d-%H%M%S")
|
||||
backup_dir_name = f"{backup_name}_{timestamp}"
|
||||
|
||||
if target_path is not None:
|
||||
backup_path = self._backup_root.joinpath(target_path, backup_dir_name)
|
||||
else:
|
||||
backup_path = self._backup_root.joinpath(backup_dir_name)
|
||||
|
||||
shutil.copytree(source_path, backup_path)
|
||||
|
||||
Logger.print_ok(
|
||||
f"Successfully backed up '{source_path}' to '{backup_path}'"
|
||||
)
|
||||
return backup_path
|
||||
|
||||
except Exception as e:
|
||||
Logger.print_error(f"Failed to backup directory '{source_path}': {e}")
|
||||
return None
|
||||
@@ -14,8 +14,8 @@ from typing import Any, Callable, List, TypeVar
|
||||
|
||||
from components.klipper import KLIPPER_REPO_URL
|
||||
from components.moonraker import MOONRAKER_REPO_URL
|
||||
from core.backup_manager.backup_manager import BackupManager
|
||||
from core.logger import DialogType, Logger
|
||||
from core.services.backup_service import BackupService
|
||||
from core.submodules.simple_config_parser.src.simple_config_parser.simple_config_parser import (
|
||||
SimpleConfigParser,
|
||||
)
|
||||
@@ -374,8 +374,8 @@ class KiauhSettings:
|
||||
kill()
|
||||
|
||||
def _migrate_repo_config(self) -> None:
|
||||
bm = BackupManager()
|
||||
if not bm.backup_file(CUSTOM_CFG):
|
||||
svc = BackupService()
|
||||
if not svc.backup_file(CUSTOM_CFG):
|
||||
Logger.print_dialog(
|
||||
DialogType.ERROR,
|
||||
[
|
||||
|
||||
@@ -12,6 +12,7 @@ Specialized for handling Klipper style config files.
|
||||
- Option Block: A line starting with a word, followed by a `:` or `=` and a newline
|
||||
- Comment: A line starting with a `#` or `;`
|
||||
- Blank: A line containing only whitespace characters
|
||||
- SaveConfig: Klippers auto-generated SAVE_CONFIG section that can be found at the very end of the config file
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -49,6 +49,9 @@ LINE_COMMENT_RE = re.compile(r"^\s*[#;].*")
|
||||
# - the line MUST contain only whitespace characters
|
||||
EMPTY_LINE_RE = re.compile(r"^\s*$")
|
||||
|
||||
SAVE_CONFIG_START_RE = re.compile(r"^#\*# <-+ SAVE_CONFIG -+>$")
|
||||
SAVE_CONFIG_CONTENT_RE = re.compile(r"^#\*#.*$")
|
||||
|
||||
BOOLEAN_STATES = {
|
||||
"1": True,
|
||||
"yes": True,
|
||||
|
||||
@@ -18,7 +18,7 @@ from ..simple_config_parser.constants import (
|
||||
LINE_COMMENT_RE,
|
||||
OPTION_RE,
|
||||
OPTIONS_BLOCK_START_RE,
|
||||
SECTION_RE, LineType, INDENT,
|
||||
SECTION_RE, LineType, INDENT, SAVE_CONFIG_START_RE, SAVE_CONFIG_CONTENT_RE,
|
||||
)
|
||||
|
||||
_UNSET = object()
|
||||
@@ -61,25 +61,34 @@ class SimpleConfigParser:
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.header: List[str] = []
|
||||
self.save_config_block: List[str] = []
|
||||
self.config: Dict = {}
|
||||
self.current_section: str | None = None
|
||||
self.current_opt_block: str | None = None
|
||||
self.in_option_block: bool = False
|
||||
|
||||
def _match_section(self, line: str) -> bool:
|
||||
"""Wheter or not the given line matches the definition of a section"""
|
||||
"""Whether the given line matches the definition of a section"""
|
||||
return SECTION_RE.match(line) is not None
|
||||
|
||||
def _match_option(self, line: str) -> bool:
|
||||
"""Wheter or not the given line matches the definition of an option"""
|
||||
"""Whether the given line matches the definition of an option"""
|
||||
return OPTION_RE.match(line) is not None
|
||||
|
||||
def _match_options_block_start(self, line: str) -> bool:
|
||||
"""Wheter or not the given line matches the definition of a multiline option"""
|
||||
"""Whether the given line matches the definition of a multiline option"""
|
||||
return OPTIONS_BLOCK_START_RE.match(line) is not None
|
||||
|
||||
def _match_save_config_start(self, line: str) -> bool:
|
||||
"""Whether the given line matches the definition of a save config start"""
|
||||
return SAVE_CONFIG_START_RE.match(line) is not None
|
||||
|
||||
def _match_save_config_content(self, line: str) -> bool:
|
||||
"""Whether the given line matches the definition of a save config content"""
|
||||
return SAVE_CONFIG_CONTENT_RE.match(line) is not None
|
||||
|
||||
def _match_line_comment(self, line: str) -> bool:
|
||||
"""Wheter or not the given line matches the definition of a comment"""
|
||||
"""Whether the given line matches the definition of a comment"""
|
||||
return LINE_COMMENT_RE.match(line) is not None
|
||||
|
||||
def _match_empty_line(self, line: str) -> bool:
|
||||
@@ -124,6 +133,14 @@ class SimpleConfigParser:
|
||||
element["value"].append(line.strip()) # indentation is removed
|
||||
break
|
||||
|
||||
elif self._match_save_config_start(line):
|
||||
self.current_opt_block = None
|
||||
self.save_config_block.append(line)
|
||||
|
||||
elif self._match_save_config_content(line):
|
||||
self.current_opt_block = None
|
||||
self.save_config_block.append(line)
|
||||
|
||||
elif self._match_empty_line(line) or self._match_line_comment(line):
|
||||
self.current_opt_block = None
|
||||
|
||||
@@ -185,6 +202,11 @@ class SimpleConfigParser:
|
||||
if not last_line.endswith("\n"):
|
||||
f.write("\n")
|
||||
|
||||
if self.save_config_block:
|
||||
for line in self.save_config_block:
|
||||
f.write(line)
|
||||
f.write("\n")
|
||||
|
||||
def get_sections(self) -> List[str]:
|
||||
"""Return a list of all section names, but exclude any section starting with '#_'"""
|
||||
return list(
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
# a comment at the very top
|
||||
# should be treated as the file header
|
||||
|
||||
# up to the first section, including all blank lines
|
||||
|
||||
[section_1]
|
||||
option_1: value_1
|
||||
option_1_1: True # this is a boolean
|
||||
option_1_2: 5 ; this is an integer
|
||||
option_1_3: 1.123 #;this is a float
|
||||
|
||||
[section_2] ; comment
|
||||
option_2: value_2
|
||||
|
||||
; comment
|
||||
|
||||
[section_3]
|
||||
option_3: value_3 # comment
|
||||
|
||||
[section_4]
|
||||
# comment
|
||||
option_4: value_4
|
||||
|
||||
[section number 5]
|
||||
#option_5: value_5
|
||||
option_5 = this.is.value-5
|
||||
multi_option:
|
||||
# these are multi-line values
|
||||
value_5_1
|
||||
value_5_2 ; here is a comment
|
||||
value_5_3
|
||||
option_5_1: value_5_1
|
||||
|
||||
[gcode_macro M117]
|
||||
rename_existing: M117.1
|
||||
gcode:
|
||||
{% if rawparams %}
|
||||
{% set escaped_msg = rawparams.split(';', 1)[0].split('\x23', 1)[0]|replace('"', '\\"') %}
|
||||
SET_DISPLAY_TEXT MSG="{escaped_msg}"
|
||||
RESPOND TYPE=command MSG="{escaped_msg}"
|
||||
{% else %}
|
||||
SET_DISPLAY_TEXT
|
||||
{% endif %}
|
||||
|
||||
# SDCard 'looping' (aka Marlin M808 commands) support
|
||||
#
|
||||
# Support SDCard looping
|
||||
[sdcard_loop]
|
||||
[gcode_macro M486]
|
||||
gcode:
|
||||
# Parameters known to M486 are as follows:
|
||||
# [C<flag>] Cancel the current object
|
||||
# [P<index>] Cancel the object with the given index
|
||||
# [S<index>] Set the index of the current object.
|
||||
# If the object with the given index has been canceled, this will cause
|
||||
# the firmware to skip to the next object. The value -1 is used to
|
||||
# indicate something that isn’t an object and shouldn’t be skipped.
|
||||
# [T<count>] Reset the state and set the number of objects
|
||||
# [U<index>] Un-cancel the object with the given index. This command will be
|
||||
# ignored if the object has already been skipped
|
||||
|
||||
{% if 'exclude_object' not in printer %}
|
||||
{action_raise_error("[exclude_object] is not enabled")}
|
||||
{% endif %}
|
||||
|
||||
{% if 'T' in params %}
|
||||
EXCLUDE_OBJECT RESET=1
|
||||
|
||||
{% for i in range(params.T | int) %}
|
||||
EXCLUDE_OBJECT_DEFINE NAME={i}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
{% if 'C' in params %}
|
||||
EXCLUDE_OBJECT CURRENT=1
|
||||
{% endif %}
|
||||
|
||||
{% if 'P' in params %}
|
||||
EXCLUDE_OBJECT NAME={params.P}
|
||||
{% endif %}
|
||||
|
||||
{% if 'S' in params %}
|
||||
{% if params.S == '-1' %}
|
||||
{% if printer.exclude_object.current_object %}
|
||||
EXCLUDE_OBJECT_END NAME={printer.exclude_object.current_object}
|
||||
{% endif %}
|
||||
{% else %}
|
||||
EXCLUDE_OBJECT_START NAME={params.S}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
{% if 'U' in params %}
|
||||
EXCLUDE_OBJECT RESET=1 NAME={params.U}
|
||||
{% endif %}
|
||||
|
||||
#*# <---------------------- SAVE_CONFIG ---------------------->
|
||||
#*# DO NOT EDIT THIS BLOCK OR BELOW. The contents are auto-generated.
|
||||
#*#
|
||||
#*# [bed_mesh default]
|
||||
#*# version = 1
|
||||
#*# points =
|
||||
#*# -0.152500, -0.133125, -0.113125, -0.159375, -0.232500
|
||||
#*# -0.095000, -0.078750, -0.068125, -0.133125, -0.235000
|
||||
#*# -0.092500, -0.040625, 0.004375, -0.077500, -0.193125
|
||||
#*# -0.073750, 0.023750, 0.085625, 0.026875, -0.085000
|
||||
#*# -0.140625, 0.038125, 0.126250, 0.097500, 0.003750
|
||||
#*# tension = 0.2
|
||||
#*# min_x = 26.0
|
||||
#*# algo = bicubic
|
||||
#*# y_count = 5
|
||||
#*# mesh_y_pps = 2
|
||||
#*# min_y = 5.0
|
||||
#*# x_count = 5
|
||||
#*# max_y = 174.0
|
||||
#*# mesh_x_pps = 2
|
||||
#*# max_x = 194.0
|
||||
@@ -0,0 +1,22 @@
|
||||
#*# any content
|
||||
#*#
|
||||
#*# DO NOT EDIT THIS BLOCK OR BELOW. The contents are auto-generated.
|
||||
#*#
|
||||
#*# [bed_mesh default]
|
||||
#*# version = 1
|
||||
#*# points =
|
||||
#*# -0.152500, -0.133125, -0.113125, -0.159375, -0.232500
|
||||
#*# -0.095000, -0.078750, -0.068125, -0.133125, -0.235000
|
||||
#*# -0.092500, -0.040625, 0.004375, -0.077500, -0.193125
|
||||
#*# -0.073750, 0.023750, 0.085625, 0.026875, -0.085000
|
||||
#*# -0.140625, 0.038125, 0.126250, 0.097500, 0.003750
|
||||
#*# tension = 0.2
|
||||
#*# min_x = 26.0
|
||||
#*# algo = bicubic
|
||||
#*# y_count = 5
|
||||
#*# mesh_y_pps = 2
|
||||
#*# min_y = 5.0
|
||||
#*# x_count = 5
|
||||
#*# max_y = 174.0
|
||||
#*# mesh_x_pps = 2
|
||||
#*# max_x = 194.0
|
||||
@@ -0,0 +1,6 @@
|
||||
#*# leading space prevents match
|
||||
random
|
||||
*# not starting with hash-star-hash
|
||||
# *# spaced out
|
||||
<- SAVE_CONFIG ->
|
||||
;#*# semicolon first
|
||||
@@ -0,0 +1,37 @@
|
||||
# ======================================================================= #
|
||||
# Copyright (C) 2024 Dominik Willner <th33xitus@gmail.com> #
|
||||
# #
|
||||
# https://github.com/dw-0/simple-config-parser #
|
||||
# #
|
||||
# This file may be distributed under the terms of the GNU GPLv3 license #
|
||||
# ======================================================================= #
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from src.simple_config_parser.simple_config_parser import SimpleConfigParser
|
||||
from tests.utils import load_testdata_from_file
|
||||
|
||||
BASE_DIR = Path(__file__).parent.joinpath("test_data")
|
||||
MATCHING_TEST_DATA_PATH = BASE_DIR.joinpath("matching_data.txt")
|
||||
NON_MATCHING_TEST_DATA_PATH = BASE_DIR.joinpath("non_matching_data.txt")
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def parser():
|
||||
return SimpleConfigParser()
|
||||
|
||||
|
||||
def test_matching_lines(parser):
|
||||
"""Alle Zeilen in matching_data.txt sollen als Save-Config-Content erkannt werden."""
|
||||
matching_lines = load_testdata_from_file(MATCHING_TEST_DATA_PATH)
|
||||
for line in matching_lines:
|
||||
assert parser._match_save_config_content(line) is True, f"Line should be a save config content: {line!r}"
|
||||
|
||||
|
||||
def test_non_matching_lines(parser):
|
||||
"""Alle Zeilen in non_matching_data.txt sollen NICHT als Save-Config-Content erkannt werden."""
|
||||
non_matching_lines = load_testdata_from_file(NON_MATCHING_TEST_DATA_PATH)
|
||||
for line in non_matching_lines:
|
||||
assert parser._match_save_config_content(line) is False, f"Line should not be a save config content: {line!r}"
|
||||
@@ -0,0 +1,6 @@
|
||||
#*# <- SAVE_CONFIG ->
|
||||
#*# <---- SAVE_CONFIG ---->
|
||||
#*# <------------------- SAVE_CONFIG ------------------->
|
||||
#*# <---------------------- SAVE_CONFIG ---------------------->
|
||||
#*# <----- SAVE_CONFIG ->
|
||||
#*# <- SAVE_CONFIG ----------------->
|
||||
@@ -0,0 +1,13 @@
|
||||
#*#<- SAVE_CONFIG ->
|
||||
#*# <-SAVE_CONFIG ->
|
||||
#*# <- SAVE_CONFIG->
|
||||
#*# <- SAVE_CONFIG -> extra
|
||||
#*# SAVE_CONFIG ---->
|
||||
#*# < SAVE_CONFIG >
|
||||
# *# <- SAVE_CONFIG ->
|
||||
<- SAVE_CONFIG ->
|
||||
random text
|
||||
#*# <---------------------- SAVE_CONFIG ---------------------->
|
||||
#*# <---------------------- SAVE_CONFIG ----------------------> #*#
|
||||
#*# <-------------------------------------------->
|
||||
#*# SAVE_CONFIG
|
||||
@@ -0,0 +1,37 @@
|
||||
# ======================================================================= #
|
||||
# Copyright (C) 2024 Dominik Willner <th33xitus@gmail.com> #
|
||||
# #
|
||||
# https://github.com/dw-0/simple-config-parser #
|
||||
# #
|
||||
# This file may be distributed under the terms of the GNU GPLv3 license #
|
||||
# ======================================================================= #
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from src.simple_config_parser.simple_config_parser import SimpleConfigParser
|
||||
from tests.utils import load_testdata_from_file
|
||||
|
||||
BASE_DIR = Path(__file__).parent.joinpath("test_data")
|
||||
MATCHING_TEST_DATA_PATH = BASE_DIR.joinpath("matching_data.txt")
|
||||
NON_MATCHING_TEST_DATA_PATH = BASE_DIR.joinpath("non_matching_data.txt")
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def parser():
|
||||
return SimpleConfigParser()
|
||||
|
||||
|
||||
def test_matching_lines(parser):
|
||||
"""Test that all lines in the matching data file are correctly identified as save config start lines."""
|
||||
matching_lines = load_testdata_from_file(MATCHING_TEST_DATA_PATH)
|
||||
for line in matching_lines:
|
||||
assert parser._match_save_config_start(line) is True, f"Line should be a save config start: {line!r}"
|
||||
|
||||
|
||||
def test_non_matching_lines(parser):
|
||||
"""Test that all lines in the non-matching data file are correctly identified as not save config start lines."""
|
||||
non_matching_lines = load_testdata_from_file(NON_MATCHING_TEST_DATA_PATH)
|
||||
for line in non_matching_lines:
|
||||
assert parser._match_save_config_start(line) is False, f"Line should not be a save config start: {line!r}"
|
||||
@@ -13,7 +13,7 @@ from src.simple_config_parser.simple_config_parser import SimpleConfigParser
|
||||
from tests.utils import load_testdata_from_file
|
||||
|
||||
BASE_DIR = Path(__file__).parent.parent.joinpath("assets")
|
||||
CONFIG_FILES = ["test_config_1.cfg", "test_config_2.cfg", "test_config_3.cfg"]
|
||||
CONFIG_FILES = ["test_config_1.cfg", "test_config_2.cfg", "test_config_3.cfg", "test_config_4.cfg"]
|
||||
|
||||
|
||||
@pytest.fixture(params=CONFIG_FILES)
|
||||
|
||||
@@ -150,9 +150,9 @@ class ExtensionSubmenu(BaseMenu):
|
||||
if website or repo:
|
||||
links_lines: List[str] = ["Links:"]
|
||||
if website:
|
||||
links_lines.append(f"- Website: {website}")
|
||||
links_lines.append(f"● {website}")
|
||||
if repo:
|
||||
links_lines.append(f"- GitHub: {repo}")
|
||||
links_lines.append(f"● {repo}")
|
||||
|
||||
links_text = Logger.format_content(
|
||||
links_lines,
|
||||
|
||||
@@ -9,12 +9,13 @@
|
||||
|
||||
import os
|
||||
import shutil
|
||||
from datetime import datetime
|
||||
from typing import List
|
||||
|
||||
from components.klipper.klipper import Klipper
|
||||
from core.backup_manager.backup_manager import BackupManager
|
||||
from core.instance_manager.instance_manager import InstanceManager
|
||||
from core.logger import Logger
|
||||
from core.services.backup_service import BackupService
|
||||
from core.submodules.simple_config_parser.src.simple_config_parser.simple_config_parser import (
|
||||
SimpleConfigParser,
|
||||
)
|
||||
@@ -109,11 +110,13 @@ class GcodeShellCmdExtension(BaseExtension):
|
||||
Logger.warn(f"Unable to create example config: {e}")
|
||||
|
||||
# backup each printer.cfg before modification
|
||||
bm = BackupManager()
|
||||
svc = BackupService()
|
||||
timestamp = datetime.now().strftime("%Y%m%d-%H%M%S")
|
||||
for instance in instances:
|
||||
bm.backup_file(
|
||||
instance.cfg_file,
|
||||
custom_filename=f"{instance.suffix}.printer.cfg",
|
||||
svc.backup_file(
|
||||
source_path=instance.cfg_file,
|
||||
target_path=f"{instance.data_dir.name}/config_{timestamp}",
|
||||
target_name=instance.cfg_file.name,
|
||||
)
|
||||
|
||||
# add section to printer.cfg if not already defined
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
"module": "gcode_shell_cmd_extension",
|
||||
"maintained_by": "dw-0",
|
||||
"display_name": "G-Code Shell Command",
|
||||
"description": ["Run a shell commands from gcode."]
|
||||
"description": ["Run a shell commands from gcode."],
|
||||
"updates": false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
"maintained_by": "Staubgeborener",
|
||||
"display_name": "Klipper-Backup",
|
||||
"description": ["Backup all your Klipper files to GitHub"],
|
||||
"website": "https://klipperbackup.xyz",
|
||||
"repo": "https://github.com/Staubgeborener/klipper-backup",
|
||||
"updates": true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
"module": "mainsail_theme_installer_extension",
|
||||
"maintained_by": "dw-0",
|
||||
"display_name": "Mainsail Theme Installer",
|
||||
"description": ["Install Mainsail Themes maintained by the Mainsail community."]
|
||||
"description": ["Install Mainsail Themes maintained by the Mainsail community."],
|
||||
"website": "https://docs.mainsail.xyz/theming/themes",
|
||||
"updates": false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from core.backup_manager import BACKUP_ROOT_DIR
|
||||
from core.constants import SYSTEMD
|
||||
|
||||
# repo
|
||||
@@ -23,7 +22,6 @@ MOBILERAKER_LOG_NAME = "mobileraker.log"
|
||||
# directories
|
||||
MOBILERAKER_DIR = Path.home().joinpath("mobileraker_companion")
|
||||
MOBILERAKER_ENV_DIR = Path.home().joinpath("mobileraker-env")
|
||||
MOBILERAKER_BACKUP_DIR = BACKUP_ROOT_DIR.joinpath("mobileraker-backups")
|
||||
|
||||
# files
|
||||
MOBILERAKER_INSTALL_SCRIPT = MOBILERAKER_DIR.joinpath("scripts/install.sh")
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
"description": [
|
||||
"Companion for Mobileraker, enabling push notification for Klipper using Moonraker."
|
||||
],
|
||||
"repo": "https://github.com/Clon1998/mobileraker_companion",
|
||||
"updates": true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,13 +13,12 @@ from typing import List
|
||||
|
||||
from components.klipper.klipper import Klipper
|
||||
from components.moonraker.moonraker import Moonraker
|
||||
from core.backup_manager.backup_manager import BackupManager
|
||||
from core.instance_manager.instance_manager import InstanceManager
|
||||
from core.logger import DialogType, Logger
|
||||
from core.services.backup_service import BackupService
|
||||
from core.settings.kiauh_settings import KiauhSettings
|
||||
from extensions.base_extension import BaseExtension
|
||||
from extensions.mobileraker import (
|
||||
MOBILERAKER_BACKUP_DIR,
|
||||
MOBILERAKER_DIR,
|
||||
MOBILERAKER_ENV_DIR,
|
||||
MOBILERAKER_INSTALL_SCRIPT,
|
||||
@@ -179,14 +178,14 @@ class MobilerakerExtension(BaseExtension):
|
||||
)
|
||||
|
||||
def _backup_mobileraker_dir(self) -> None:
|
||||
bm = BackupManager()
|
||||
bm.backup_directory(
|
||||
MOBILERAKER_DIR.name,
|
||||
source=MOBILERAKER_DIR,
|
||||
target=MOBILERAKER_BACKUP_DIR,
|
||||
svc = BackupService()
|
||||
svc.backup_directory(
|
||||
source_path=MOBILERAKER_DIR,
|
||||
backup_name="mobileraker",
|
||||
target_path="mobileraker",
|
||||
)
|
||||
bm.backup_directory(
|
||||
MOBILERAKER_ENV_DIR.name,
|
||||
source=MOBILERAKER_ENV_DIR,
|
||||
target=MOBILERAKER_BACKUP_DIR,
|
||||
svc.backup_directory(
|
||||
source_path=MOBILERAKER_ENV_DIR,
|
||||
backup_name="mobileraker-env",
|
||||
target_path="mobileraker",
|
||||
)
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
"- 25FPS High-Def Webcam Streaming",
|
||||
"- Free 4.9-Star Mobile App"
|
||||
],
|
||||
"website": "https://obico.io",
|
||||
"repo": "github.com/TheSpaghettiDetective/moonraker-obico",
|
||||
"updates": true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
"- Live Gcode preview",
|
||||
"- And much much more!"
|
||||
],
|
||||
"repo": "https://github.com/crysxd/OctoApp-Plugin",
|
||||
"updates": true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
"- Real-time Notifications",
|
||||
"- Live Streaming, and More!"
|
||||
],
|
||||
"website": "https://octoeverywhere.com",
|
||||
"repo": "github.com/QuinnDamerell/OctoPrint-OctoEverywhere",
|
||||
"updates": true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
"maintained_by": "Kragrathea",
|
||||
"display_name": "PrettyGCode for Klipper",
|
||||
"description": ["3D G-Code viewer for Klipper"],
|
||||
"repo": "https://github.com/Kragrathea/pgcode",
|
||||
"updates": true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
{
|
||||
"metadata": {
|
||||
"index": 10,
|
||||
"module": "simply_print_extension",
|
||||
"maintained_by": "dw-0",
|
||||
"display_name": "SimplyPrint",
|
||||
"description": [
|
||||
"3D Printer Cloud Management Software.",
|
||||
"\n\n",
|
||||
"3D printing doesn't have to be a complicated, analog, SD card-filled experience; step into the future of modern 3D printing"
|
||||
]
|
||||
}
|
||||
"metadata": {
|
||||
"index": 10,
|
||||
"module": "simply_print_extension",
|
||||
"maintained_by": "dw-0",
|
||||
"display_name": "SimplyPrint",
|
||||
"description": [
|
||||
"3D Printer Cloud Management Software.",
|
||||
"\n\n",
|
||||
"3D printing doesn't have to be a complicated, analog, SD card-filled experience; step into the future of modern 3D printing"
|
||||
],
|
||||
"website": "https://simplyprint.io",
|
||||
"repo": "https://github.com/SimplyPrint",
|
||||
"updates": false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
"\n\n",
|
||||
"Note: This extension installs Spoolman using Docker. Docker must be installed on your system before installing Spoolman."
|
||||
],
|
||||
"repo": "https://github.com/Donkie/Spoolman",
|
||||
"updates": true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,9 +15,9 @@ from components.moonraker.moonraker import Moonraker
|
||||
from components.moonraker.services.moonraker_instance_service import (
|
||||
MoonrakerInstanceService,
|
||||
)
|
||||
from core.backup_manager.backup_manager import BackupManager
|
||||
from core.instance_manager.instance_manager import InstanceManager
|
||||
from core.logger import DialogType, Logger
|
||||
from core.services.backup_service import BackupService
|
||||
from extensions.base_extension import BaseExtension
|
||||
from extensions.spoolman import (
|
||||
SPOOLMAN_COMPOSE_FILE,
|
||||
@@ -123,16 +123,15 @@ class SpoolmanExtension(BaseExtension):
|
||||
"Failed to remove Spoolman image! Please remove it manually."
|
||||
)
|
||||
|
||||
# backup Spoolman directory to ~/spoolman_data-<timestamp> before removing it
|
||||
try:
|
||||
bm = BackupManager()
|
||||
result = bm.backup_directory(
|
||||
f"{SPOOLMAN_DIR.name}_data",
|
||||
source=SPOOLMAN_DIR,
|
||||
target=SPOOLMAN_DIR.parent,
|
||||
svc = BackupService()
|
||||
success = svc.backup_directory(
|
||||
source_path=SPOOLMAN_DIR,
|
||||
backup_name="spoolman",
|
||||
target_path="spoolman",
|
||||
)
|
||||
if result:
|
||||
Logger.print_ok(f"Spoolman data backed up to {result}")
|
||||
if success:
|
||||
Logger.print_ok(f"Spoolman data backed up to {success}")
|
||||
Logger.print_status("Removing Spoolman directory...")
|
||||
if run_remove_routines(SPOOLMAN_DIR):
|
||||
Logger.print_ok("Spoolman directory removed!")
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
"maintained_by": "nlef",
|
||||
"display_name": "Moonraker Telegram Bot",
|
||||
"description": ["Control your printer with the Telegram messenger app."],
|
||||
"project_url": "https://github.com/nlef/moonraker-telegram-bot",
|
||||
"repo": "https://github.com/nlef/moonraker-telegram-bot",
|
||||
"updates": true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,6 @@ from pathlib import Path
|
||||
from typing import Literal
|
||||
|
||||
from components.klipper import (
|
||||
KLIPPER_BACKUP_DIR,
|
||||
KLIPPER_DIR,
|
||||
KLIPPER_ENV_DIR,
|
||||
KLIPPER_REQ_FILE,
|
||||
@@ -21,7 +20,6 @@ from components.klipper import (
|
||||
from components.klipper.klipper import Klipper
|
||||
from components.klipper.klipper_utils import install_klipper_packages
|
||||
from components.moonraker import (
|
||||
MOONRAKER_BACKUP_DIR,
|
||||
MOONRAKER_DIR,
|
||||
MOONRAKER_ENV_DIR,
|
||||
MOONRAKER_REQ_FILE,
|
||||
@@ -30,10 +28,10 @@ from components.moonraker.moonraker import Moonraker
|
||||
from components.moonraker.services.moonraker_setup_service import (
|
||||
install_moonraker_packages,
|
||||
)
|
||||
from core.backup_manager.backup_manager import BackupManager, BackupManagerException
|
||||
from core.instance_manager.instance_manager import InstanceManager
|
||||
from core.logger import Logger
|
||||
from utils.git_utils import GitException, get_repo_name, git_clone_wrapper
|
||||
from core.services.backup_service import BackupService
|
||||
from utils.git_utils import GitException, git_clone_wrapper
|
||||
from utils.instance_utils import get_instances
|
||||
from utils.sys_utils import (
|
||||
VenvCreationFailedException,
|
||||
@@ -52,7 +50,6 @@ def run_switch_repo_routine(
|
||||
repo_dir: Path = KLIPPER_DIR if name == "klipper" else MOONRAKER_DIR
|
||||
env_dir: Path = KLIPPER_ENV_DIR if name == "klipper" else MOONRAKER_ENV_DIR
|
||||
req_file = KLIPPER_REQ_FILE if name == "klipper" else MOONRAKER_REQ_FILE
|
||||
backup_dir: Path = KLIPPER_BACKUP_DIR if name == "klipper" else MOONRAKER_BACKUP_DIR
|
||||
_type = Klipper if name == "klipper" else Moonraker
|
||||
|
||||
# step 1: stop all instances
|
||||
@@ -64,19 +61,17 @@ def run_switch_repo_routine(
|
||||
env_dir_backup_path: Path | None = None
|
||||
|
||||
try:
|
||||
# step 2: backup old repo and env
|
||||
org, _ = get_repo_name(repo_dir)
|
||||
backup_dir = backup_dir.joinpath(org)
|
||||
bm = BackupManager()
|
||||
repo_dir_backup_path = bm.backup_directory(
|
||||
repo_dir.name,
|
||||
repo_dir,
|
||||
backup_dir,
|
||||
svc = BackupService()
|
||||
svc.backup_directory(
|
||||
source_path=repo_dir,
|
||||
backup_name=name,
|
||||
target_path=name,
|
||||
)
|
||||
env_dir_backup_path = bm.backup_directory(
|
||||
env_dir.name,
|
||||
env_dir,
|
||||
backup_dir,
|
||||
env_backup_name: str = f"{name if name == 'moonraker' else 'klippy'}-env"
|
||||
svc.backup_directory(
|
||||
source_path=env_dir,
|
||||
backup_name=env_backup_name,
|
||||
target_path=name,
|
||||
)
|
||||
|
||||
if not (repo_url or branch):
|
||||
@@ -101,10 +96,6 @@ def run_switch_repo_routine(
|
||||
|
||||
Logger.print_ok(f"Switched to {repo_url} at branch {branch}!")
|
||||
|
||||
except BackupManagerException as e:
|
||||
Logger.print_error(f"Error during backup of repository: {e}")
|
||||
raise RepoSwitchFailedException(e)
|
||||
|
||||
except (GitException, VenvCreationFailedException) as e:
|
||||
# if something goes wrong during cloning or recreating the virtualenv,
|
||||
# we restore the backup of the repo and env
|
||||
@@ -122,6 +113,9 @@ def run_switch_repo_routine(
|
||||
Logger.print_error(f"Something went wrong: {e}")
|
||||
return
|
||||
|
||||
except Exception as e:
|
||||
raise RepoSwitchFailedException(e)
|
||||
|
||||
Logger.print_status(f"Restarting all {_type.__name__} instances ...")
|
||||
InstanceManager.start_all(instances)
|
||||
|
||||
|
||||
@@ -18,9 +18,9 @@ from components.klipper.klipper import Klipper
|
||||
from components.moonraker.moonraker import Moonraker
|
||||
from core.constants import (
|
||||
GLOBAL_DEPS,
|
||||
PRINTER_DATA_BACKUP_DIR,
|
||||
)
|
||||
from core.logger import DialogType, Logger
|
||||
from core.services.backup_service import BackupService
|
||||
from core.types.color import Color
|
||||
from core.types.component_status import ComponentStatus, StatusCode
|
||||
from utils.git_utils import (
|
||||
@@ -152,11 +152,8 @@ def get_install_status(
|
||||
|
||||
|
||||
def backup_printer_config_dir() -> None:
|
||||
# local import to prevent circular import
|
||||
from core.backup_manager.backup_manager import BackupManager
|
||||
|
||||
instances: List[Klipper] = get_instances(Klipper)
|
||||
bm = BackupManager()
|
||||
svc = BackupService()
|
||||
|
||||
if not instances:
|
||||
Logger.print_info("Unable to find directory to backup!")
|
||||
@@ -164,10 +161,10 @@ def backup_printer_config_dir() -> None:
|
||||
return
|
||||
|
||||
for instance in instances:
|
||||
bm.backup_directory(
|
||||
instance.data_dir.name,
|
||||
source=instance.base.cfg_dir,
|
||||
target=PRINTER_DATA_BACKUP_DIR,
|
||||
svc.backup_directory(
|
||||
source_path=instance.base.cfg_dir,
|
||||
target_path=f"{instance.data_dir.name}",
|
||||
backup_name="config",
|
||||
)
|
||||
|
||||
|
||||
|
||||
18
klipper_repos.txt.example
Normal file
18
klipper_repos.txt.example
Normal file
@@ -0,0 +1,18 @@
|
||||
# This file acts as an example file.
|
||||
#
|
||||
# 1) Make a copy of this file and rename it to 'klipper_repos.txt'
|
||||
# 2) Add your custom Klipper repository to the bottom of that copy
|
||||
# 3) Save the file
|
||||
#
|
||||
# Back in KIAUH you can now go into -> [Settings] and use action '2' to set a different Klipper repository
|
||||
#
|
||||
# Make sure to always separate the repository and the branch with a ','.
|
||||
# <repository>,<branch> -> https://github.com/Klipper3d/klipper,master
|
||||
# If you omit a branch, it will always default to 'master'
|
||||
#
|
||||
# You are allowed to omit the 'https://github.com/' part of the repository URL
|
||||
# Down below are now a few examples of what is considered as valid:
|
||||
https://github.com/Klipper3d/klipper,master
|
||||
https://github.com/Klipper3d/klipper
|
||||
Klipper3d/klipper,master
|
||||
Klipper3d/klipper
|
||||
84
mkdocs.yml
84
mkdocs.yml
@@ -1,84 +0,0 @@
|
||||
site_name: KIAUH Documentation
|
||||
site_description: Documentation for the Klipper Installation And Update Helper
|
||||
repo_url: https://github.com/dw-0/kiauh
|
||||
repo_name: dw-0/kiauh
|
||||
edit_uri: edit/master/docs
|
||||
|
||||
copyright: Copyright © 2025 Dominik Willner
|
||||
|
||||
theme:
|
||||
name: material
|
||||
logo: assets/logo.png
|
||||
favicon: assets/logo.png
|
||||
icon:
|
||||
repo: fontawesome/brands/github
|
||||
palette:
|
||||
- media: "(prefers-color-scheme: light)"
|
||||
scheme: default
|
||||
primary: blue-grey
|
||||
accent: cyan
|
||||
toggle:
|
||||
icon: material/weather-night
|
||||
name: Switch to dark mode
|
||||
- media: "(prefers-color-scheme: dark)"
|
||||
scheme: slate
|
||||
primary: blue-grey
|
||||
accent: cyan
|
||||
toggle:
|
||||
icon: material/weather-sunny
|
||||
name: Switch to light mode
|
||||
features:
|
||||
- navigation.instant
|
||||
- navigation.tracking
|
||||
- navigation.sections
|
||||
- navigation.expand
|
||||
- navigation.indexes
|
||||
- navigation.top
|
||||
- toc.follow
|
||||
- content.code.copy
|
||||
|
||||
plugins:
|
||||
- search
|
||||
- git-revision-date-localized:
|
||||
enable_creation_date: true
|
||||
- mkdocstrings:
|
||||
handlers:
|
||||
python:
|
||||
paths: [.]
|
||||
options:
|
||||
docstring_style: google
|
||||
|
||||
markdown_extensions:
|
||||
- admonition
|
||||
- pymdownx.details
|
||||
- pymdownx.superfences
|
||||
- pymdownx.highlight:
|
||||
anchor_linenums: true
|
||||
- pymdownx.inlinehilite
|
||||
- pymdownx.snippets
|
||||
- pymdownx.superfences
|
||||
- tables
|
||||
- attr_list
|
||||
- md_in_html
|
||||
|
||||
nav:
|
||||
- Home: index.md
|
||||
- Installation:
|
||||
- setup/raspberry-pi-setup.md
|
||||
- setup/installation.md
|
||||
- Configuration: configuration.md
|
||||
- Extensions:
|
||||
- extensions/index.md
|
||||
- extensions/gcode-shell-command.md
|
||||
- Development:
|
||||
- development/contributing.md
|
||||
- development/changelog.md
|
||||
|
||||
extra:
|
||||
social:
|
||||
- icon: simple/github
|
||||
link: https://github.com/dw-0
|
||||
- icon: simple/kofi
|
||||
link: https://ko-fi.com/dw__0
|
||||
- icon: simple/paypal
|
||||
link: https://www.paypal.com/paypalme/dwillner0
|
||||
@@ -2,7 +2,7 @@
|
||||
requires-python = ">=3.8"
|
||||
|
||||
[project.optional-dependencies]
|
||||
dev=["ruff", "pyright"]
|
||||
dev=["ruff", "mypy"]
|
||||
|
||||
[tool.ruff]
|
||||
required-version = ">=0.9.10"
|
||||
@@ -20,3 +20,14 @@ quote-style = "double"
|
||||
|
||||
[tool.ruff.lint]
|
||||
extend-select = ["I"]
|
||||
|
||||
[tool.mypy]
|
||||
python_version = "3.8"
|
||||
platform = "linux"
|
||||
# strict = true # TODO: enable this once everything is else is handled
|
||||
check_untyped_defs = true
|
||||
ignore_missing_imports = true
|
||||
warn_redundant_casts = true
|
||||
warn_unused_ignores = true
|
||||
warn_return_any = true
|
||||
warn_unreachable = true
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
mkdocs-material
|
||||
mkdocs
|
||||
mkdocstrings[python]
|
||||
mkdocs-git-revision-date-localized-plugin
|
||||
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
@@ -27,7 +27,7 @@ function moonraker_systemd() {
|
||||
###
|
||||
# any moonraker client that uses "moonraker" in its own name must be blacklisted using
|
||||
# this variable, otherwise they will be falsely recognized as moonraker instances
|
||||
blacklist="obico"
|
||||
blacklist="obico|hmi|telegram-bot"
|
||||
|
||||
ignore="${SYSTEMD}/moonraker-(${blacklist}).service"
|
||||
match="${SYSTEMD}/moonraker(-[0-9a-zA-Z]+)?.service"
|
||||
@@ -153,11 +153,20 @@ import shlex
|
||||
import re
|
||||
import pathlib
|
||||
import logging
|
||||
import json
|
||||
|
||||
from typing import Tuple, Dict, List, Any
|
||||
|
||||
def _get_distro_info() -> Dict[str, Any]:
|
||||
try:
|
||||
import distro
|
||||
except ModuleNotFoundError:
|
||||
pass
|
||||
else:
|
||||
return dict(
|
||||
distro_id=distro.id(),
|
||||
distro_version=distro.version(),
|
||||
aliases=distro.like().split()
|
||||
)
|
||||
release_file = pathlib.Path("/etc/os-release")
|
||||
release_info: Dict[str, str] = {}
|
||||
with release_file.open("r") as f:
|
||||
@@ -193,6 +202,9 @@ class SysDepsParser:
|
||||
version = distro_info.get("distro_version")
|
||||
if version:
|
||||
self.distro_version = _convert_version(version)
|
||||
self.vendor: str = ""
|
||||
if pathlib.Path("/etc/rpi-issue").is_file():
|
||||
self.vendor = "raspberry-pi"
|
||||
|
||||
def _parse_spec(self, full_spec: str) -> str | None:
|
||||
parts = full_spec.split(";", maxsplit=1)
|
||||
@@ -237,6 +249,9 @@ class SysDepsParser:
|
||||
elif req_var == "distro_id":
|
||||
left_op: str | Tuple[int | str, ...] = self.distro_id
|
||||
right_op = dep_parts[2].strip().strip("\"'")
|
||||
elif req_var == "vendor":
|
||||
left_op = self.vendor
|
||||
right_op = dep_parts[2].strip().strip("\"'")
|
||||
elif req_var == "distro_version":
|
||||
if not self.distro_version:
|
||||
logging.info(
|
||||
@@ -305,12 +320,12 @@ system_deps = {
|
||||
"python3-virtualenv", "python3-dev", "libopenjp2-7", "libsodium-dev",
|
||||
"zlib1g-dev", "libjpeg-dev", "packagekit",
|
||||
"wireless-tools; distro_id != 'ubuntu' or distro_version <= '24.04'",
|
||||
"iw; distro_id == 'ubuntu' and distro_version >= '24.10'", "curl",
|
||||
"build-essential"
|
||||
"iw; distro_id == 'ubuntu' and distro_version >= '24.10'",
|
||||
"python3-libcamera; vendor == 'raspberry-pi' and distro_version >= '11'",
|
||||
"curl", "build-essential"
|
||||
],
|
||||
}
|
||||
system_deps_json = pathlib.Path("$package_json")
|
||||
system_deps = json.loads(system_deps_json.read_bytes())
|
||||
# *** SYSTEM DEPENDENCIES END ***
|
||||
parser = SysDepsParser()
|
||||
pkgs = parser.parse_dependencies(system_deps)
|
||||
if pkgs:
|
||||
|
||||
Reference in New Issue
Block a user