diff --git a/latest/README.md b/latest/README.md new file mode 100644 index 0000000..c342581 --- /dev/null +++ b/latest/README.md @@ -0,0 +1,112 @@ +## Anycubic Kobra 2 Series Tools + +This repository contains tools for the Anycubic Kobra 2 Series 3D printers. + +> [!WARNING] +> This repository is unmaintained. I'm not using the printer anymore and I don't have the time to maintain it. I'm archiving this repository and will delete it in 1 year. So if you want to use it, you need to fork it and maintain it yourself. Same goes for the firmware repository. + +### Documentation + +Documentation can be found in the `docs` directory. + +- [VERSIONS.md](docs/VERSIONS.md) - Known firmware versions. +- [LINKS.md](docs/LINKS.md) - Useful things/addons for the printer. +- [OPTIONS.md](docs/OPTIONS.md) - Options for the firmware. +- [GCODE_COMMANDS.md](docs/GCODE_COMMANDS.md) - GCODE commands. +- [MQTT_API.md](docs/MQTT_API.md) - MQTT API. +- [COMMANDS.md](docs/COMMANDS.md) - Useful commands. +- [PRINTER_CFG.md](docs/PRINTER_CFG.md) - Printer.cfg things. +- [EMMC_BACKUP.md](docs/EMMC_BACKUP.md) - How to backup the EMMC. +- [EMMC_RESTORE.md](docs/EMMC_RESTORE.md) - How to restore the EMMC. +- [ENTER_FEL_MODE.md](docs/ENTER_FEL_MODE.md) - How to enter FEL mode. +- [DOWNLOAD_SDK.md](docs/DOWNLOAD_SDK.md) - How to download the SDK. +- [OLD_INFO.md](docs/OLD_INFO.md) - Old information. +- [CREDITS.md](docs/CREDITS.md) - Credits. + +## Usage + +> [!IMPORTANT] +> Please backup all files in `/user` so you don't lose access to anycubic cloud and OTA updates. You can use the [EMMC_BACKUP.md](./EMMC_BACKUP.md) guide to backup the whole system. But backing up `/user` is enough to keep access to anycubic cloud and OTA updates. +> +> You could use `dd` command to backup also. + +> [!CAUTION] > **IF YOU DO NOT BACKUP OR DELETE THE FILES IN `/user` YOU WILL LOSE ACCESS TO ANYCUBIC CLOUD AND OTA UPDATES. YOU HAVE BEEN WARNED.** +> +> Everything you do is on **your** own risk. I am **not** responsible for any damage you do to your printer. + +> [!IMPORTANT] +> For python scripts to work you need to have installed `python3` and `pip3` and then install the required packages with `pip3 install -r requirements.txt`. + +#### 1. Clone the repository. + +#### 2. Make sure you have uart cable connected and have downgraded to version `2.3.9` so you can continue with the next steps. + +> [!NOTE] +> If not you may not get any uart output at all. To downgrade just put the `2.3.9` version on usb like you always do. + +#### 3. Place the `.bin .zip .swu` firmware files in the `FW` directory. + +> [!TIP] +> If you don't have firmware files, you can use the script `fwdl.sh ` to download in the folder `FW` the version for the printer model you need. The supported models are `K2Pro`, `K2Plus` and `K2Max`. The version is given in the format `X.Y.Z` like `3.0.9`. + +#### 4. Run `unpack.sh ` to unpack the selected firmware update file. + +The supported file extensions are `bin`, `zip` and `swu`. The result is in the folder `unpacked`. + +#### 5. Modify the options file `options.cfg`. + +To select the options you need and run `patch.sh` to patch the firmware files in the `unpacked` folder. + +The result is still in the folder `unpacked`. You may manually modify the current state of the files if needed. You can also prepare different configuration files for different needs based on the default file `options.cfg`. The custom configuration file is provided as parameter: `patch.sh `. If no parameter is provided, the file `options.cfg` will be used. + +#### 6. Run `pack.sh` to pack the firmware files from the folder `unpacked`. + +The result is the file `update/update.swu`. + +At the end, if you selected `ssh` and `root_access` with a password, you will be asked if you want to upload the update automatically through ssh. If your printer has already a custom update (with ssh and root password) you can type `y` and press `enter`. The update will be transferred to the printer, executed and the printer will reboot. Otherwise, press enter to exit and follow the next step for USB update. + +#### 7. If your printer is still with the original firmware, you have to make root access first. + +Then replace the `/etc/swupdate_public.pem` in the printer with the one from the `RESOURCES` directory or create your own (make a copy first of the original `/etc/swupdate_public.pem` key in case you want to return to the original `ota` updates). + +Then apply the newly generated custom software `update/update.swu` by USB update (place the file `update.swu` in the folder `update` on the root of a FAT32 formatted USB disk). If your printer already has custom update installed, then you can directly apply the new update by USB update. + +To do all this a little easier you can just use `build.sh` and it will run all the steps for you. + +> [!WARNING] +> This repository is a work in progress and may contain bugs or may not work as expected any pull requests are welcome. + +> [!NOTE] +> Default password for the root access (custom firmware with UART and SSH) is `toor` but it can be changed in the `options.cfg` file. + +> [!IMPORTANT] +> Start the scripts directly by `./script_name.sh ` to be started by the requested `bash` shell. Shells like `sh` are not compatible at this time. + +> [!IMPORTANT] +> Use only FAT32 formatted USB disk and place the file `update.swu` inside a folder `update` created at the root of the USB disk. You don't have to have a 4 GB usb. It can be 64 or 128 GB or more. You only need to format 1 partition to max 4 GB. Then FAT32 will be available. + +> [!TIP] +> In order for the auto update upload to work properly, you need to setup in advance the configuration file `auto_install.cfg`. It requires one line of text with the following information: +> `host_ip`,`user_name`,`printer_ip`,`ssh_port` +> Example: +> `192.168.1.234,root,192.168.1.242,22` +> +> Only applies if you have already rooted and installed ssh on the printer. + +### Advanced usage + +Partition map of the printer: + +![Partition map](./docs/images/partition.jpg) + +### Information + +**FW** - Place `.bin`, `.zip` or `.swu` firmware files here. + +**RESOURCES** - Contains resources for the firmware options. + +**TOOLS** - Contains tools to decrypt and encrypt firmware files and more. + +**unpacked** - Contains the unpacked firmware files. + +**update** - Contains the packed firmware files. diff --git a/latest/build.sh b/latest/build.sh new file mode 100644 index 0000000..244c097 --- /dev/null +++ b/latest/build.sh @@ -0,0 +1,260 @@ +#!/bin/bash + +project_root="$PWD" + +# Source the utils.sh file +source "$project_root/TOOLS/helpers/utils.sh" "$project_root" + +# 0 arguments: interactive mode +# 1 argument: firmware file or configuration file +# 2 arguments: firmware file and configuration file +usage() { + echo "usage : $0 [firmware_file] [configuration_file]" + exit 1 +} + +# check the required tools +check_tools "awk zip app_version.sh app_model.sh ack2_swu_encrypt.py python3" + +# set the custom encrypt tool +ENCRYPT_TOOL=$(which "ack2_swu_encrypt.py") +if [ -z "$ENCRYPT_TOOL" ]; then + # if not installed use the local copy + ENCRYPT_TOOL="TOOLS/ack2_swu_encrypt.py" +fi + +# selected fw file +selected_firmware_file="" + +# selected config file +selected_config_file="options.cfg" + +# check first for a default file set by update.bin|zip|swu +default_firmware_file="" +if [ -f "$FW_DIR/update.swu" ]; then + default_firmware_file="$FW_DIR/update.swu" +elif [ -f "$FW_DIR/update.zip" ]; then + default_firmware_file="$FW_DIR/update.zip" +elif [ -f "$FW_DIR/update.bin" ]; then + default_firmware_file="$FW_DIR/update.bin" +fi + +if [ $# -eq 0 ]; then + # no arguments provided + if [ -n "$default_firmware_file" ]; then + # but default file exists, use it + selected_firmware_file="$default_firmware_file" + fi +elif [ $# -eq 1 ]; then + # one argument provided + fw_file="$1" + fw_file_ext="${fw_file##*.}" + if [ "$fw_file_ext" = "swu" ] || [ "$fw_file_ext" = "bin" ] || [ "$fw_file_ext" = "zip" ]; then + if [ -f "$fw_file" ]; then + # it is a valid firmware file + selected_firmware_file="$fw_file" + elif [ -f "$FW_DIR/$fw_file" ]; then + selected_firmware_file="$FW_DIR/$fw_file" + else + usage + fi + else + cfg_file="$project_root/$1" + if [ -f "$cfg_file" ]; then + # it is a configuration file with ext + selected_config_file="$cfg_file" + elif [ -f "${cfg_file}.cfg" ]; then + echo "${cfg_file}.cfg" + # it is a configuration file without ext + selected_config_file="${cfg_file}.cfg" + else + usage + fi + selected_firmware_file="$default_firmware_file" + fi +elif [ $# -eq 2 ]; then + # two arguments provided + fw_file="$1" + fw_file_ext="${fw_file##*.}" + if [ "$fw_file_ext" = "swu" ] || [ "$fw_file_ext" = "bin" ] || [ "$fw_file_ext" = "zip" ]; then + if [ -f "$fw_file" ]; then + # it is a valid firmware file + selected_firmware_file="$fw_file" + elif [ -f "$FW_DIR/$fw_file" ]; then + selected_firmware_file="$FW_DIR/$fw_file" + else + usage + fi + else + usage + fi + cfg_file="$project_root/$2" + if [ -f "$cfg_file" ]; then + # it is a configuration file with ext + selected_config_file="$cfg_file" + elif [ -f "${cfg_file}.cfg" ]; then + # it is a configuration file without ext + selected_config_file="${cfg_file}.cfg" + else + usage + fi +elif [ $# -ge 3 ]; then + # 3 or more arguments provided + usage +fi + +# check the config file for build_input and build_output options +build_input="" +build_output="" +auto_install_tool="" +if [ -f "$selected_config_file" ]; then + + # parse the enabled options that have a set value + options=$(awk -F '=' '{if (! ($0 ~ /^;/) && ! ($0 ~ /^#/) && ! ($0 ~ /^$/) && ! ($2 == "")) print $1}' "$selected_config_file") + + # for each enabled option + for option in $options; do + parameters=$(awk -F '=' "{if (! (substr(\$0,1,1) == \"#\") && ! (substr(\$0,1,1) == \";\") && ! (\$1 == \"\") && ! (\$2 == \"\") && (\$1 ~ /$option/ ) ) print \$2}" "$selected_config_file" | head -n 1) + # replace the project root requests + parameter="${parameters/@/"$project_root"}" + # remove the leading and ending double quotes + parameter=$(echo "$parameter" | sed -e 's/^"//' -e 's/"$//') + # remove the leading and ending single quotes + parameter=$(echo "$parameter" | sed -e 's/^'\''//' -e 's/'\''$//') + if [ "$option" = "build_input" ]; then + build_input="$parameter" + fi + if [ "$option" = "build_output" ]; then + build_output="$parameter" + fi + if [ "$option" = "auto_install" ]; then + auto_install_tool="$parameter" + fi + done +fi + +if [ -z "$selected_firmware_file" ] && [ -n "$build_input" ] && [ -f "$build_input" ]; then + # no fw file provided but the config file has a valid fw file set, use that file + selected_firmware_file="$build_input" +fi + +if [ -z "$selected_firmware_file" ]; then + + # No firmware file selected by the user: interactive mode + + # Check if firmware exists in the FW folder else ask the user to download it + + # List all files in $FW_DIR and check if there are any files .zip, .bin or .swu + # If there are files, ask the user if they want to use the files in the FW folder else ask the user to download the firmware + + # Get all files in the FW folder that are .zip, .bin or .swu + all_files=$(ls $FW_DIR | grep -E ".zip|.bin|.swu") + + # If there are no files in the FW folder, ask the user to download the firmware + if [ -z "$all_files" ]; then + read -p "No firmware files found in the FW folder. Do you want to download the firmware? (y/n) " -n 1 -r + if [[ $REPLY =~ ^[Yy]$ ]]; then + echo + read -p "Enter version: " par_version + read -p "Enter model: " par_model + echo "Downloading firmware..." + # Run fwdl.sh with the model and version as parameters + $project_root/fwdl.sh $par_model $par_version + all_files=$(ls $FW_DIR | grep -E ".zip|.bin|.swu") + else + echo + echo "Please download the firmware and place it in the FW folder" + exit 2 + fi + fi + + # If there are files in the FW folder, ask the user if they want to use the files in the FW folder + if [ -n "$all_files" ]; then + # List all firmwares and ask user to pick an available firmware version + echo "Available firmware versions:" + for file in $all_files; do + echo $file + done + read -p "Which file do you want to use? " firmware_file + if [ -f "$FW_DIR/$firmware_file" ]; then + echo "Using firmware $firmware_file" + else + echo "Firmware file not found" + exit 3 + fi + fi + selected_firmware_file="$FW_DIR/$firmware_file" +fi + +# Unpack the firmware +echo "Unpacking firmware..." +"$project_root/unpack.sh" "$selected_firmware_file" +if [ $? -ne 0 ]; then + echo "Failed to unpack firmware" + exit 4 +fi + +# Patch the firmware +echo "Patching firmware..." +"$project_root/patch.sh" "$selected_config_file" +if [ $? -ne 0 ]; then + echo "Failed to patch firmware" + exit 5 +fi + +# Build the firmware +echo "Building firmware..." +"$project_root/pack.sh" +if [ $? -ne 0 ]; then + echo "Failed to build firmware" + exit 6 +fi + +# Process the output file if set +if [ -n "$build_output" ]; then + + # try to find out the app version (like app_ver="3.1.0") + def_target="$ROOTFS_DIR/app/app" + app_ver=$("$app_version_tool" "$def_target") + if [ $? != 0 ]; then + echo -e "${RED}ERROR: Cannot find the app version ${NC}" + exit 4 + fi + + # try to find out the model + app_model=$("$app_model_tool" "$def_target") + if [ $? != 0 ]; then + echo -e "${RED}ERROR: Cannot find the app model ${NC}" + exit 5 + fi + + rm -f "$project_root/update.bin" + rm -f "$project_root/update.zip" + zip -r "$project_root/update.zip" update + $ENCRYPT_TOOL -i "$project_root/update.zip" -o "$project_root/update.bin" -m "$app_model" -v "$app_ver" + /bin/cp -f "$project_root/update.bin" "$build_output" + rm -f "$project_root/update.zip" + rm -f "$project_root/update.bin" +fi + +# use the auto install tool if present +if [ -f "$auto_install_tool" ]; then + # Ask if the user wants to attempt to auto install the update now. If yes then run the auto install script + read -r -p "Do you want to attempt to auto install the update? [y/N] " response + if [[ "$response" =~ ^([yY][eE][sS]|[yY])$ ]]; then + # Run the auto update tool + if [[ "$auto_install_tool" == *.py ]]; then + python3 "$auto_install_tool" + else + "$auto_install_tool" + fi + fi +fi + +echo +echo -e "${YELLOW}Selected firmware file: $selected_firmware_file ${NC}" +echo -e "${YELLOW}Selected configuration file: $selected_config_file ${NC}" +echo -e "${GREEN}Firmware build complete ${NC}" +echo + +exit 0 diff --git a/latest/fwdl.sh b/latest/fwdl.sh new file mode 100644 index 0000000..00ca232 --- /dev/null +++ b/latest/fwdl.sh @@ -0,0 +1,175 @@ +#!/bin/bash + +project_root="$PWD" + +# Source the utils.sh file +source "$project_root/TOOLS/helpers/utils.sh" "$project_root" + +# check the parameters for a model and version +if [ $# != 2 ]; then + echo "usage : $0 " + echo "example: $0 K2Pro 3.0.9" + echo "example: $0 K2Plus \"3.0.0 3.0.5 3.0.9 3.1.0\"" + echo "example: $0 K2Max all" + echo "example: $0 K2Pro latest" + echo "example: $0 all all" + echo "example: $0 K2Pro scan" + exit 1 +fi + +par_models="$1" +par_versions="$2" + +stop_after_error=1 +downloaded=0 + +if [ "$par_versions" = "latest" ] || [ "$par_versions" = "LATEST" ] || [ "$par_versions" = "last" ] || [ "$par_versions" = "LAST" ]; then + par_versions=$(curl -s "https://raw.githubusercontent.com/AGG2017/ACK2-Webserver/master/latest_version.txt") +fi + +if [ "$par_versions" = "all" ] || [ "$par_versions" = "ALL" ]; then + par_versions="2.3.9 3.0.3 3.0.5 3.0.9 3.1.0 3.1.2" +fi + +if [ "$par_versions" = "scan" ] || [ "$par_versions" = "SCAN" ]; then + latest=$(curl -s "https://raw.githubusercontent.com/AGG2017/ACK2-Webserver/master/latest_version.txt") + ver_h=$(echo "$latest" | awk -F. '{print $1}') + ver_m=$(echo "$latest" | awk -F. '{print $2}') + ver_l=$(echo "$latest" | awk -F. '{print $3}') + par_versions="" + for i in {1..100}; do + ver_l=$((ver_l + 1)) + if [ $ver_l -ge 10 ]; then + ver_l=0 + ver_m=$((ver_m + 1)) + if [ $ver_m -ge 10 ]; then + ver_m=0 + ver_h=$((ver_h + 1)) + fi + fi + version="${ver_h}.${ver_m}.${ver_l}" + if [ -z "$par_versions" ]; then + par_versions="${version}" + else + par_versions="${par_versions} ${version}" + fi + done + stop_after_error=0 +fi + +if [ "$par_models" = "all" ] || [ "$par_models" = "ALL" ]; then + par_models="K2Pro K2Plus K2Max" +fi + +# check the required tools +check_tools "curl wc awk" + +for par_model in $par_models; do + + # check the model + if [ "$par_model" != "K2Pro" ] && [ "$par_model" != "K2Plus" ] && [ "$par_model" != "K2Max" ]; then + echo -e "${RED}ERROR: Unsupported model '$par_model' ${NC}" + exit 1 + fi + + for par_version in $par_versions; do + echo -e "${YELLOW}Processing model $par_model version $par_version ...${NC}" + ver_int=${par_version//./} + if [ "$ver_int" -le 309 ]; then + # old url format up to 3.0.9 + url_bin="https://cdn.cloud-universe.anycubic.com/ota/${par_model}/AC104_${par_model}_1.1.0_${par_version}_update.bin" + file_bin="FW/AC104_${par_model}_1.1.0_${par_version}_update.bin" + rm -f "$file_bin" + curl "$url_bin" --output "$file_bin" + result=$(grep "NoSuchKey" "$file_bin") + file_size=$(wc -c "$file_bin" | awk '{print $1}') + if [ -n "$result" ] || [ "$file_size" -le 1000000 ]; then + rm -f "$file_bin" + # no bin update available, try zip update + url_zip="https://cdn.cloud-universe.anycubic.com/ota/${par_model}/AC104_${par_model}_1.1.0_${par_version}_update.zip" + file_zip="FW/AC104_${par_model}_1.1.0_${par_version}_update.zip" + rm -f "$file_zip" + curl "$url_zip" --output "$file_zip" + result=$(grep "NoSuchKey" "$file_zip") + file_size=$(wc -c "$file_zip" | awk '{print $1}') + if [ -n "$result" ] || [ "$file_size" -le 1000000 ]; then + rm -f "$file_zip" + # no bin and no zip update available + echo -e "${RED}ERROR: Cannot find an update for this model and version ${NC}" + if [ $stop_after_error -eq 1 ]; then + exit 3 + fi + else + downloaded=$((downloaded + 1)) + fi + fi + else + if [ "$ver_int" -le 311 ]; then + # url format 3.1.0 + par_model_str="k2PRO" + par_model_id="20021" + if [ "$par_model" = "K2Plus" ]; then + par_model_str="k2PLUS" + par_model_id="20022" + fi + if [ "$par_model" = "K2Max" ]; then + par_model_str="k2MAX" + par_model_id="20023" + fi + url_bin="https://cdn.cloud-universe.anycubic.com/ota/prod/${par_model_id}/AC104_${par_model_str}_V${par_version}.bin" + file_bin="FW/AC104_${par_model}_1.1.0_${par_version}_update.bin" + rm -f "$file_bin" + curl "$url_bin" --output "$file_bin" + result=$(grep "NoSuchKey" "$file_bin") + file_size=$(wc -c "$file_bin" | awk '{print $1}') + if [ -n "$result" ] || [ "$file_size" -le 1000000 ]; then + rm -f "$file_bin" + # no bin update available + echo -e "${RED}ERROR: Cannot find an update for this model and version ${NC}" + if [ $stop_after_error -eq 1 ]; then + exit 4 + fi + else + downloaded=$((downloaded + 1)) + fi + else + # url format 3.1.2+ + par_model_str="k2+Pro" + if [ "$par_model" = "K2Plus" ]; then + par_model_str="k2+Plus" + fi + if [ "$par_model" = "K2Max" ]; then + par_model_str="k2+Max" + fi + url_bin="https://cdn.cloud-universe.anycubic.com/ota/${par_model}/ChituUpgrade_${par_model_str}_V${par_version}.bin" + file_bin="FW/AC104_${par_model}_1.1.0_${par_version}_update.bin" + rm -f "$file_bin" + curl "$url_bin" --output "$file_bin" + result=$(grep "NoSuchKey" "$file_bin") + file_size=$(wc -c "$file_bin" | awk '{print $1}') + if [ -n "$result" ] || [ "$file_size" -le 1000000 ]; then + rm -f "$file_bin" + # no bin update available + echo -e "${RED}ERROR: Cannot find an update for this model and version ${NC}" + if [ $stop_after_error -eq 1 ]; then + exit 4 + fi + else + downloaded=$((downloaded + 1)) + fi + fi + fi + done +done + +if [ $downloaded -eq 0 ]; then + echo "" + echo -e "${RED}ERROR: Cannot find an update for this model ${NC}" + echo "" + exit 5 +fi + +echo "" +echo -e "${GREEN}DONE! The requested firmware has been downloaded in the folder FW ${NC}" +echo "" +exit 0 diff --git a/latest/options-example.cfg b/latest/options-example.cfg new file mode 100644 index 0000000..17c58e9 --- /dev/null +++ b/latest/options-example.cfg @@ -0,0 +1,129 @@ +#------------------------------------------------------------------------------------------------ +# 'options.cfg' is the default configuration file used by 'patch.sh' +# You can provide another configuration file as a parameter to 'patch.sh' +# All available patching options are listed below +# Disable patching an option by using '#' or ';' as a first character in the option line +# Use single or double quotes for the option value. Do not use spaces inside the quotes. +# Use list of values separated by a space if needed because duplicated option are not supported +# In square brackets you may place the name(s) of other option(s) required by a given option. +# This will not auto include the required option(s) but will be used to validate the option integrity. +# IMPORTANT!!! Copy and rename this file to 'options.cfg' and remove the 'example' from the filename. +#------------------------------------------------------------------------------------------------- + +# For used with build.sh only! +# Select the input firmware file to be used for this configuration file +#build_input="@/FW/310/update.swu" + +# For used with build.sh only! +# Compatible to versions 3.0.5+ only! +# Select the output update file copy of the encrypted result update.bin +#build_output="/data/kobra-unleashed/uploads/updates/20021/310.bin" + +# Enable the custom updates by using the provided public key +# When enabled, the new custom update will allow next custom updates to be directly done by USB update +# @ will be replaced by the working folder root +# Use the provided example of public and private keys or generate your own: +# Use 'openssl genrsa -out swupdate_private.pem' to generate a private key +# Use 'openssl rsa -in swupdate_private.pem -out swupdate_public.pem -outform PEM -pubout' to export the public key +# Place both keys (swupdate_private.pem and swupdate_public.pem) in the folder RESOURCES/KEYS +# Available for all firmware versions (recommended) +custom_update="@/RESOURCES/KEYS/swupdate_public.pem" + +# Enable root access by providing a custom root password hash (default password 'toor') +# Providing an empty string will allow root access without a password (not recommended) +# If disabled, you will have no root access until you find out what is the AC original root password +# Available for all firmware versions (recommended) +root_access="$1$///xTLYF$krWXTe62/dm.crd6CH4HW0" + +# Enable the UART at boot for access the uboot shell and for root login +# UART access is needed for backup/restore and for root login when you don't have ssh +# Use "2.3.9" uboot from version '2.3.9' (recommended) +# From version 3.0.3 the UART is disabled +# Can be used for version 3.0.3 and above +uart="2.3.9" + +# Enable opkg (+5MB to the update, +10MB to the rootfs) +# Use it only when you plan to install more packages from the printer +# Available for all firmware versions +opkg="default" + +# Enable the SSH server, use 'dropbear' type ssh +# Available for all firmware versions (recommended) +ssh="dropbear" + +# Set the authorized keys file for the ssh remote access with keys (instead of a password) +# Provide the file with your public key(s). You must have the private key set in your ssh client +# Available for all firmware versions (recommended) +# authorized_keys="@/RESOURCES/KEYS/authorized_keys" [ssh] + +# Enable custom web server +# Use "webfs-v5" with default port 8000 or "webfs-v5:port" for a custom port +# "webfs-v5" uses static libraries with memory footprint less than 400kb (0.4% RAM), no dependencies +# Browse http://printer_ip:8000 to the home page of the custom webserver +# More web pages will be added soon +# Depends on the option "app_nocamera" only for versions 3.0.5+ +# No dependency for versions below 3.0.5 +# Available for versions 2.3.9 ... 3.1.0 (recommended) +webserver="webfs-v5:8000" [app_nocamera] + +# Enable Python 3 (+14MB to the update, +25MB to the rootfs) +# Select the version you need. Python might be required by some other options +# Available for all firmware versions +# python="3.11" + +# Change the banner to a custom one. (Recommended when using custom firmware) +# Available for all firmware versions +banner="banner" + +# Redirect the MQTT communication +# This option will disable the use of the original AC cloud service +# and the mobile app will stop working +# Can be used when a custom cloud service is needed. +# Example of a custom cloud service with web interface is Kobra Unleashed +# More information: https://github.com/anjomro/kobra-unleashed +# Replace the URL below with the URL of your MQTT server +# You also need to transfer to the printer your keys in +# the /user folder by ssh as explained in the project page +# Always keep a copy of the original keys in case you want to go back to AC cloud +# Available for all firmware versions +modify_mqtt="localhost.mr-a.de" +# localhost.mr-a.de will just redirect the MQTT to the localhost 127.0.0.1 to be used with a local MQTT server + +# Delete the Bluetooth support because it's not used by Kobra Unleashed +# and can't be used since we replace mqtt anyways +# Available for versions 3.0.5+ +bluetooth="default" + +# Patch the app to check the captive portal URLs less often (originally every 2s) +# It is used to detect if the internet connection is established and alive +# This patch will produce less unwanted web traffic if enabled +# Available settings are: 5s, 10s, 20s, 30s and 60s +# Available for versions 3.0.5+ +app_net_ready="30s" + +# Modify the hardcoded app DNS +# For parameter(s) provide "old_dns|new_dns" +# Available hardcoded DNS in the app: +# "8.8.8.8", "208.67.222.222", "114.114.114.114", "223.5.5.5" +# You can replace one, more or all of them +# Example: app_dns="208.67.222.222|4.4.4.4" "223.5.5.5|8.8.4.4" +app_dns="208.67.222.222|1.1.1.1" + +# Patch the app to stop supporting webcams. This allows a custom camera support +# Available for versions 3.0.5+ (recommended if webcam is used) +app_nocamera="default" + +# Add a script for custom initializations at startup +# like starting a custom MQTT server, webcam steaming, etc. +# Place startup scripts inside this script. +#startup_script="startup.sh" + +# Enable kobra unleashed prod version test +# https://github.com/anjomro/kobra-unleashed/tree/go-server +#kobra_unleashed="kobra_unleashed-v1" + +# Enable this option if you want to be executed after the packing script completes +# if you want to install the generated update by ssh +# You have to have ssh and root_access enabled, and setup the auto_install.cfg +#auto_install="@/TOOLS/custom_install.sh" +#auto_install="@/TOOLS/auto_install.py" \ No newline at end of file diff --git a/latest/pack.sh b/latest/pack.sh new file mode 100644 index 0000000..a7e13a6 --- /dev/null +++ b/latest/pack.sh @@ -0,0 +1,130 @@ +#!/bin/bash + +project_root="$PWD" + +# Source the utils.sh file +source "$project_root/TOOLS/helpers/utils.sh" "$project_root" + +# files needed +FILES="sw-description sw-description.sig boot-resource uboot boot0 kernel rootfs dsp0 cpio_item_md5" + +# check the required tools +check_tools "grep md5sum openssl wc awk sha256sum mksquashfs" + +# remove the last created update +rm -rf update +mkdir update + +# pack the squashfs-root folder +cd unpacked || exit 2 + +echo -e "${YELLOW}Deleting the existing rootfs${NC}" +rm -rf rootfs +mksquashfs squashfs-root rootfs -comp xz -all-root + +# check if the updated rootfs can fit in the partitions rootfsA/B +file_size=$(wc -c rootfs | awk '{print $1}') +if [ "$file_size" -ge 134217729 ]; then + echo -e "${RED}ERROR: The size of the file 'unpacked/rootfs' is larger than the max 128MB allowed.\Please disable some of the less important options and try again! ${NC}" + cd .. + exit 3 +fi + +# check the input files +for i in $FILES; do + if [ "$i" != "cpio_item_md5" ] && [ ! -f "$i" ]; then + echo -e "${RED}ERROR: Cannot find the input file '$i' ${NC}" + cd .. + exit 4 + fi +done + +# update sw-description +rm -f cpio_item_md5 +for i in $FILES; do + if [ "$i" != "cpio_item_md5" ] && [ "$i" != "sw-description" ] && [ "$i" != "sw-description.sig" ]; then + hash_new=$(sha256sum "$i" | awk '{print $1}') + hash_old=$(awk -F= 'BEGIN{v=""} $1~"filename"{v=$2} $1~"sha256"{gsub(/"| |;/,"",v); gsub(/"| |;/,"",$2); print v " " $2}' sw-description | grep "$i" | head -1 | awk '{print $2}') + if [ -n "$hash_old" ]; then + sed -i -e "s/$hash_old/$hash_new/g" sw-description + else + echo -e "${RED}ERROR: Cannot find the hash for: '$i' ${NC}" + cd .. + exit 5 + fi + fi +done + +# create cpio_item_md5 +rm -f cpio_item_md5 +for i in $FILES; do + if [ "$i" != "cpio_item_md5" ]; then + hash=$(md5sum "$i") + echo "$hash" >>cpio_item_md5 + fi +done + +# sign the file sw-description +rm -f sw-description.sig +openssl dgst -sha256 -sign ../RESOURCES/KEYS/swupdate_private.pem sw-description >sw-description.sig + +# pack the input files as update.swu +for i in $FILES; do echo "$i"; done | cpio -ov -H crc >../update/update.swu + +cd .. + +echo "" +echo -e "${GREEN}Packing done: Use the file update/update.swu to do USB update${NC}" +echo "" + +# select a config file +selected_config_file="options.cfg" +if [ $# -eq 1 ]; then + cfg_file="$project_root/$1" + if [ -f "$cfg_file" ]; then + # it is a configuration file with ext + selected_config_file="$cfg_file" + elif [ -f "${cfg_file}.cfg" ]; then + echo "${cfg_file}.cfg" + # it is a configuration file without ext + selected_config_file="${cfg_file}.cfg" + fi +fi + +# check if the auto update is enabled and get the selected tool +auto_install_tool="" +if [ -f "$selected_config_file" ]; then + + # parse the enabled options that have a set value + options=$(awk -F '=' '{if (! ($0 ~ /^;/) && ! ($0 ~ /^#/) && ! ($0 ~ /^$/) && ! ($2 == "")) print $1}' "$selected_config_file") + + # for each enabled option + for option in $options; do + parameters=$(awk -F '=' "{if (! (substr(\$0,1,1) == \"#\") && ! (substr(\$0,1,1) == \";\") && ! (\$1 == \"\") && ! (\$2 == \"\") && (\$1 ~ /$option/ ) ) print \$2}" "$selected_config_file" | head -n 1) + # replace the project root requests + parameter="${parameters/@/"$project_root"}" + # remove the leading and ending double quotes + parameter=$(echo "$parameter" | sed -e 's/^"//' -e 's/"$//') + # remove the leading and ending single quotes + parameter=$(echo "$parameter" | sed -e 's/^'\''//' -e 's/'\''$//') + if [ "$option" = "auto_install" ]; then + auto_install_tool="$parameter" + fi + done +fi + +# use the auto install tool if present +if [ -f "$auto_install_tool" ]; then + # Ask if the user wants to attempt to auto install the update now. If yes then run the auto install script + read -r -p "Do you want to attempt to auto install the update? [y/N] " response + if [[ "$response" =~ ^([yY][eE][sS]|[yY])$ ]]; then + # Run the auto update tool + if [[ "$auto_install_tool" == *.py ]]; then + python3 "$auto_install_tool" + else + "$auto_install_tool" + fi + fi +fi + +exit 0 diff --git a/latest/patch.sh b/latest/patch.sh new file mode 100644 index 0000000..db4a3aa --- /dev/null +++ b/latest/patch.sh @@ -0,0 +1,99 @@ +#!/bin/bash + +project_root="$PWD" + +# Source the utils.sh file +source "$project_root/TOOLS/helpers/utils.sh" "$project_root" + +# the default options file +optionsfile="options.cfg" + +# the result of installed options log +installed_options="installed_options.log" + +# check the parameters for a custom options file +if [ $# == 1 ]; then + optionsfile="$1" + if [ ! -f "$optionsfile" ]; then + optionsfile="${optionsfile}.cfg" + fi +fi + +# check the options file +if [ ! -f "$optionsfile" ]; then + echo -e "${RED}ERROR: Cannot find the '$optionsfile' file ${NC}" + exit 1 +fi + +check_tools "awk head sed" + +# remove the old result file for the installed options +rm -f "$installed_options" + +# parse the enabled options that have a set value +options=$(awk -F '=' '{if (! ($0 ~ /^;/) && ! ($0 ~ /^#/) && ! ($0 ~ /^$/) && ! ($2 == "")) print $1}' "$optionsfile") + +# for each enabled option +for option in $options; do + + # skip the options build_input & build_output that are used only in build.sh + # skip the option auto_install that is used only in pack.sh + if [ "$option" = "build_input" ] || [ "$option" = "build_output" ] || [ "$option" = "auto_install" ]; then + continue + fi + + echo -e "${PURPLE}Processing option '$option' ...${NC}" + # parse the parameters (only from the first found option) + # duplicated options are not supported, if needed use list of parameters for the same option: + # startup_script="script1.sh" "script2.sh" "script3.sh" + parameters=$(awk -F '=' "{if (! (substr(\$0,1,1) == \"#\") && ! (substr(\$0,1,1) == \";\") && ! (\$1 == \"\") && ! (\$2 == \"\") && (\$1 ~ /$option/ ) ) print \$2}" "$optionsfile" | head -n 1) + # replace the project root requests + parameters="${parameters/@/"$project_root"}" + # execute the script + opt_script="${OPTIONS_DIR}/${option}/${option}.sh" + if [ ! -f "$opt_script" ]; then + echo -e "${RED}ERROR: Cannot find the file '$opt_script' ${NC}" + exit 3 + fi + # for each parameter in the list + for parameter in $parameters; do + # remove the leading and ending double quotes + param=$(echo "$parameter" | sed -e 's/^"//' -e 's/"$//') + # remove the leading and ending single quotes + par=$(echo "$param" | sed -e 's/^'\''//' -e 's/'\''$//') + # remove the leading and ending square brackets + req_option=$(echo "$par" | sed -e 's/^\[//' -e 's/\]$//') + if [ "$par" == "$req_option" ]; then + # current parameter requires processing + "$opt_script" "$project_root" "$par" + if [ $? -ne 0 ]; then + # errors found, exit + echo "Errors found! The patching has been canceled." + exit 4 + fi + # set this option as already installed + echo "$option=\"$par\"" >>"$installed_options" + else + # this is an option requirement that needs validation + found="" + for opt in $options; do + if [ "$opt" == "$req_option" ]; then + found="$opt" + break + fi + done + if [ -z "$found" ]; then + # required option is missing or not enabled + echo -e "${RED}ERROR: Option '$option' requires option '$req_option' which is missing or not enabled. ${NC}" + exit 5 + fi + echo -e "${GREEN}Option '$option' requires option '$req_option'. This requirement was successfully validated. ${NC}" + fi + done +done + +echo "" +echo -e "${GREEN}DONE! The selected options are successfully processed.${NC}\nYou may do manually more changes in the 'unpacked' folder if needed." +echo "" + +exit 0 diff --git a/latest/requirements.txt b/latest/requirements.txt new file mode 100644 index 0000000..ba70660 --- /dev/null +++ b/latest/requirements.txt @@ -0,0 +1,3 @@ +pycryptodome==3.21.0 +paramiko==3.5.0 +scp==0.15.0 diff --git a/latest/unpack.sh b/latest/unpack.sh new file mode 100644 index 0000000..34c29a3 --- /dev/null +++ b/latest/unpack.sh @@ -0,0 +1,101 @@ +#!/bin/bash + +project_root="$PWD" + +# Source the utils.sh file +source "$project_root/TOOLS/helpers/utils.sh" "$project_root" + +# check the number of arguments +if (($# != 1)); then + echo "Usage: ./unpack.sh update_file" + echo "Example: ./unpack.sh FW/AC104_K2Pro_1.1.0_3.0.5_update.bin" + exit 1 +fi + +UPDATE_FILE="$1" +UPDATE_FILENAME=$(basename -- "$UPDATE_FILE") +UPDATE_FILE_EXT="${UPDATE_FILENAME##*.}" + +# check the input file +if [ ! -f "$UPDATE_FILE" ]; then + echo -e "${RED}ERROR: Cannot find the input file ${UPDATE_FILE}${NC}" + exit 1 +fi + +# check the input file ext +if [ "$UPDATE_FILE_EXT" != "bin" ] && [ "$UPDATE_FILE_EXT" != "swu" ] && [ "$UPDATE_FILE_EXT" != "zip" ]; then + echo -e "${RED}ERROR: Unknown file extension '${UPDATE_FILE_EXT}'${NC}" + exit 2 +fi + +# check the required tools +check_tools "cpio unsquashfs unzip ack2_swu_decrypt.py python3" + +# set the custom decrypt tool +DECRYPT_TOOL=$(which "ack2_swu_decrypt.py") +if [ -z "$DECRYPT_TOOL" ]; then + # if not installed use the local copy + DECRYPT_TOOL="TOOLS/ack2_swu_decrypt.py" +fi + +# remove old temp files if present +rm -rf unpacked +mkdir unpacked + +# preprocessing the update file +if [ "$UPDATE_FILE_EXT" == "bin" ]; then + # decrypt the update if it is encrypted + $DECRYPT_TOOL -i "$UPDATE_FILE" -o ./unpacked/update.zip + if [ ! -f "./unpacked/update.zip" ]; then + echo -e "${RED}ERROR: Cannot find the input file './unpacked/update.zip' ${NC}" + exit 4 + fi + cd unpacked || exit 5 + unzip update.zip + rm -r update.zip + cd .. +else + if [ "$UPDATE_FILE_EXT" == "zip" ]; then + # zip file + cp "$UPDATE_FILE" ./unpacked/update.zip + cd unpacked || exit 5 + unzip update.zip + rm -r update.zip + cd .. + else + # swu file, prepare a copy of the file + mkdir ./unpacked/update + cp "$UPDATE_FILE" ./unpacked/update/update.swu + fi +fi + +# check the input file +if [ ! -f "./unpacked/update/update.swu" ]; then + echo -e "${RED}ERROR: Cannot find the input file './unpacked/update/update.swu' ${NC}" + exit 6 +fi + +# extract the update +cd unpacked || exit 7 +cpio -idv <./update/update.swu + +# verify that all needed parts exist +FILES="sw-description sw-description.sig boot-resource uboot boot0 kernel rootfs dsp0 cpio_item_md5" +for i in $FILES; do + if [ ! -f "$i" ]; then + echo -e "${RED}ERROR: Cannot find the expected update component '$i' ${NC}" + cd .. + exit 8 + fi +done + +# unpack the rootfs +unsquashfs rootfs + +cd .. + +echo "" +echo -e "${GREEN}Unpacking DONE! Check the 'unpacked' folder for the result.${NC}" +echo "" + +exit 0