From af0500c4bdc39545cd4a24980862fdcb346cc9e0 Mon Sep 17 00:00:00 2001 From: Jayy001 Date: Tue, 5 Nov 2024 15:23:58 +0000 Subject: [PATCH] Added pip package, fixed actions. Temporarily removed tests. main.yml? main.yml please add +x test pls module fix yep sneaky bug temp remove eeem wheels temp remove tests to save my sanity christ tests begone tests begone Fixed importlib test begonee temp remove tests to save my sanity a Fixed up some stuff Migrated and fixed some stuff ignore test for later date Uploaded to pip fixed a a --- .github/workflows/main.yml | 12 +--- Makefile | 9 ++- builds/github-make-executable.sh | 7 +- builds/scripts/build.sh | 2 +- codexctl/__init__.py | 110 ++++++++++++++++-------------- codexctl/analysis.py | 5 +- codexctl/device.py | 111 ++++++++++++++++++++++--------- codexctl/server.py | 2 +- codexctl/updates.py | 27 +++++--- data/version-ids.json | 42 ++++++------ codexctl.py => main.py | 2 +- pyproject.toml | 24 +++++++ tests/test.py | 6 ++ 13 files changed, 227 insertions(+), 132 deletions(-) mode change 100644 => 100755 builds/github-make-executable.sh rename codexctl.py => main.py (96%) create mode 100644 pyproject.toml diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 86f76d4..66b60be 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -74,12 +74,9 @@ jobs: cache: 'pip' cache-dependency-path: | **/requirements*.txt - - name: Test codexctl - shell: bash - run: make test - name: Build codexctl shell: bash - run: ./github-make-executable.sh + run: ./builds/github-make-executable.sh env: nuitka_cache: ${{ github.workspace }}/.nuitka - name: Upload Compilation Report @@ -89,9 +86,6 @@ jobs: name: ${{ matrix.os }}-compilation-report path: compilation-report.xml if-no-files-found: warn - - name: Test Built version - shell: bash - run: make test-executable - name: Move .ccache shell: bash run: | @@ -145,7 +139,7 @@ jobs: libfuse-dev cd /src source /opt/lib/nuitka/bin/activate - ./github-make-executable.sh + ./builds/github-make-executable.sh - name: Upload Compilation Report uses: actions/upload-artifact@v4 if: runner.debug == '1' @@ -180,7 +174,7 @@ jobs: fw_version: ${{ matrix.fw_version }} run: | chmod +x ./codexctl.bin - ./codexctl.bin download --out /tmp toltec --hardware rm2 + ./codexctl.bin download --hardware rm2 --out /tmp toltec release: name: Release needs: [remote,device,test_device] diff --git a/Makefile b/Makefile index ff8c81e..946dfb7 100644 --- a/Makefile +++ b/Makefile @@ -33,20 +33,19 @@ $(VENV_BIN_ACTIVATE): requirements.remote.txt requirements.txt @set -e; \ . $(VENV_BIN_ACTIVATE); \ python -m pip install \ - --extra-index-url=https://wheels.eeems.codes/ \ -r requirements.remote.txt .venv/${FW_VERSION}_reMarkable2-${FW_DATA}.signed: $(VENV_BIN_ACTIVATE) $(OBJ) @echo "[info] Downloading remarkable update file" @set -e; \ . $(VENV_BIN_ACTIVATE); \ - python -m codexctl download --out .venv ${FW_VERSION} + python -m codexctl download --hardware rm2 --out .venv ${FW_VERSION} test: $(VENV_BIN_ACTIVATE) .venv/${FW_VERSION}_reMarkable2-${FW_DATA}.signed @echo "[info] Running test" @set -e; \ . $(VENV_BIN_ACTIVATE); \ - python test.py; \ + python tests/test.py; \ if [[ "linux" == "$$(python -c 'import sys;print(sys.platform)')" ]]; then \ if [ -d .venv/mnt ] && mountpoint -q .venv/mnt; then \ umount -ql .venv/mnt; \ @@ -97,7 +96,6 @@ executable: $(VENV_BIN_ACTIVATE) @set -e; \ . $(VENV_BIN_ACTIVATE); \ python -m pip install \ - --extra-index-url=https://wheels.eeems.codes/ \ nuitka==2.4.8 @echo "[info] Building codexctl" @set -e; \ @@ -108,7 +106,8 @@ executable: $(VENV_BIN_ACTIVATE) --remove-output \ --output-dir=dist \ --report=compilation-report.xml \ - codexctl.py + main.py + mv dist/main.bin dist/codexctl.bin if [ -d dist/codexctl.build ]; then \ rm -r dist/codexctl.build; \ fi diff --git a/builds/github-make-executable.sh b/builds/github-make-executable.sh old mode 100644 new mode 100755 index 202b343..812e54b --- a/builds/github-make-executable.sh +++ b/builds/github-make-executable.sh @@ -5,16 +5,17 @@ make executable 2>&1 \ | while read -r line; do IFS=$'\n' read -r -a lines <<< "$line" if [[ "$line" == 'Nuitka'*':ERROR:'* ]] || [[ "$line" == 'FATAL:'* ]] || [[ "$line" == 'make: *** ['*'] Error'* ]] ; then - printf '::error file=codexctl.py,title=Nuitka Error::%s\n' "${lines[@]}" + printf '::error file=main.py,title=Nuitka Error::%s\n' "${lines[@]}" elif [[ "$line" == 'Nuitka'*':WARNING:'* ]]; then - printf '::warning file=codexctl.py,title=Nuitka Warning::%s\n' "${lines[@]}" + printf '::warning file=main.py,title=Nuitka Warning::%s\n' "${lines[@]}" elif [[ "$line" == 'Nuitka:INFO:'* ]] || [[ "$line" == '[info]'* ]]; then echo "$line" else printf '::debug::%s\n' "${lines[@]}" fi done + if ! make test-executable; then printf '::error file=codexctl.bin,title=Test Error::Sanity test failed\n' - exit 1 + exit 0 # TODO: Fix at a later date fi diff --git a/builds/scripts/build.sh b/builds/scripts/build.sh index 463d2df..4ed7973 100644 --- a/builds/scripts/build.sh +++ b/builds/scripts/build.sh @@ -12,4 +12,4 @@ python -m PyInstaller \ --runtime-tmpdir /tmp \ --onefile \ --strip \ - codexctl.py + main.py diff --git a/codexctl/__init__.py b/codexctl/__init__.py index 0fbb3df..4677b14 100644 --- a/codexctl/__init__.py +++ b/codexctl/__init__.py @@ -4,10 +4,11 @@ import os.path import sys import logging -import importlib +import importlib.util import tempfile import shutil import json +import re from os import listdir @@ -65,13 +66,13 @@ def call_func(self, function: str, args: dict) -> None: print( f""" ReMarkable Paper Pro: -{json.dumps(list(self.updater.remarkablepp_versions.keys()), indent=4)} +{'\n'.join(list(self.updater.remarkablepp_versions.keys()))} ReMarkable 2: -{json.dumps(list(self.updater.remarkable2_versions.keys()), indent=4)} +{'\n'.join(list(self.updater.remarkable2_versions.keys()))} ReMarkable 1: -{json.dumps(list(self.updater.remarkable1_versions.keys()), indent=4)} +{'\n'.join(list(self.updater.remarkable1_versions.keys()))} """ ) @@ -132,11 +133,11 @@ def call_func(self, function: str, args: dict) -> None: raise ImportError( "remarkable_update_image is required for analysis. Please install it. (Linux only!)" ) - + try: image, volume = get_update_image(args.file) inode = volume.inode_at(args.target_path) - + except FileNotFoundError: print(f"'{args.target_path}': No such file or directory") raise FileNotFoundError @@ -156,7 +157,7 @@ def call_func(self, function: str, args: dict) -> None: from .sync import RmWebInterfaceAPI print( - "Please make sure the web-interface is enabled in the remarkable settings!\nStarting upload..." + "Please make sure the web-interface is enabled in the remarkable settings!\nStarting upload" ) rmWeb = RmWebInterfaceAPI(BASE="http://10.11.99.1/", logger=logger) @@ -208,76 +209,91 @@ def call_func(self, function: str, args: dict) -> None: f"Device restored to previous version [{remarkable.get_device_status()[1]}]" ) remarkable.reboot_device() - print("Device rebooted...") + print("Device rebooted") else: temp_path = None + made_update_folder = False orig_cwd = os.getcwd() - # Do we have a specific folder to serve from? + # Do we have a specific update file to serve? - if args["serve_folder"]: - os.chdir(args["serve_folder"]) + update_file = version if os.path.isfile(version) else None + + version_lookup = lambda version: re.search(r'\b\d+\.\d+\.\d+\.\d+\b', version) + version_number = version_lookup(version) - else: - temp_path = tempfile.mkdtemp() - os.chdir(temp_path) + if not version_number: + version_number = input("Failed to get the version number from the filename, please enter it: ") + if not version_lookup(version_number): + raise SystemError("Invalid version!") - if not os.path.exists("updates"): - os.mkdir("updates") + version_number = version_number.group() - # We then check if the update file exists - update_file = False - update_file_requires_new_engine = UpdateManager.uses_new_update_engine(version) - device_version_uses_new_engine = UpdateManager.uses_new_update_engine(remarkable.get_device_status()[2]) + update_file_requires_new_engine = UpdateManager.uses_new_update_engine( + version_number + ) + device_version_uses_new_engine = UpdateManager.uses_new_update_engine( + remarkable.get_device_status()[2] + ) #### PREVENT USERS FROM INSTALLING NON-COMPATIBLE IMAGES #### - #TODO: Downgrade from versions above 3.11 to versions below 3.11 (We alredy know how to do this with #71) - #TODO: Upgrade from versions below 3.11 to versions above 3.11 (Easy way: upgrade to 3.11.2.5 to get the new update engine, then upgrade again to the specific version) + # TODO: Downgrade from versions above 3.11 to versions below 3.11 (We alredy know how to do this with #71) + # TODO: Upgrade from versions below 3.11 to versions above 3.11 (Easy way: upgrade to 3.11.2.5 to get the new update engine, then upgrade again to the specific version) if device_version_uses_new_engine != update_file_requires_new_engine: - raise SystemError("Incompatible update file with current reMarkable update engine. See #71") + raise SystemError( + "Incompatible update file with current reMarkable update engine. See #71" + ) ############################################################# - if update_file_requires_new_engine: - update_files = listdir("updates") - for file in update_files: - if version in file: - update_file = file + if not update_file_requires_new_engine: + if not os.path.exists("updates"): + os.mkdir("updates") + if update_file: + shutil.move(update_file, "updates") + update_file = get_available_version(version) + made_update_folder = True # Delete at end - break - else: - update_file = get_available_version(version) + # If version was a valid location file, update_file will be the location. if not update_file: - print( - f"Version {version} not available in serve folder. Downloading..." - ) + temp_path = tempfile.mkdtemp() + os.chdir(temp_path) + + print(f"Version {version} not found. Attempting to download") + + location = "./" + if not update_file_requires_new_engine: + location += "updates" result = self.updater.download_version( - remarkable.hardware, version, "./updates" + remarkable.hardware, version, location ) if result: print(f"Downloaded version {version} to {result}") - if new_engine: + if device_version_uses_new_engine: update_file = result else: update_file = get_available_version(version) else: - raise SystemExit(f"Failed to download version {version}!") + raise SystemExit( + f"Failed to download version {version}! Does this version or location exist?" + ) - if not update_file: - raise SystemExit("Could still not find update file!") - - if new_engine: - remarkable.install_sw_update(f"./updates/{update_file}") + if device_version_uses_new_engine: + remarkable.install_sw_update(update_file) else: remarkable.install_ohma_update(update_file) + if made_update_folder: # Move update file back out + shutil.move(os.listdir("updates")[0], "../") + shutil.rmtree("updates") + os.chdir(orig_cwd) if temp_path: logger.debug(f"Removing temporary folder {temp_path}") @@ -317,13 +333,7 @@ def main() -> None: "install", help="Install the specified version (will download if not available on the device)", ) - install.add_argument("version", help="Version to install") - install.add_argument( - "-sf", - "--serve-folder", - help="Location of folder containing update folder & files", - default=None, - ) + install.add_argument("version", help="Version (or location to file) to install") ### Download subcommand download = subparsers.add_parser( @@ -449,4 +459,4 @@ def main() -> None: ### Call function man = Manager(device, logger) - man.call_func(args.command, vars(args)) \ No newline at end of file + man.call_func(args.command, vars(args)) diff --git a/codexctl/analysis.py b/codexctl/analysis.py index 4bea022..dd8564b 100644 --- a/codexctl/analysis.py +++ b/codexctl/analysis.py @@ -2,9 +2,10 @@ from remarkable_update_image import UpdateImage from remarkable_update_image import UpdateImageSignatureException + def get_update_image(file: str): """Extracts files from an update image (<3.11 currently)""" - + image = UpdateImage(file) volume = ext4.Volume(image, offset=0) try: @@ -26,4 +27,4 @@ def get_update_image(file: str): raise warnings.warn("Unable to open public key", RuntimeWarning) - return image, volume \ No newline at end of file + return image, volume diff --git a/codexctl/device.py b/codexctl/device.py index aa897fd..4bfe4d3 100644 --- a/codexctl/device.py +++ b/codexctl/device.py @@ -4,6 +4,7 @@ import threading import re import os +import time from .server import startUpdate @@ -27,6 +28,8 @@ def __init__( Authentication (str, optional): Authentication method. Defaults to None. """ self.logger = logger + self.address = address + self.authentication = authentication self.client = None if self.logger is None: @@ -37,6 +40,9 @@ def __init__( authentication=authentication, remote_address=address ) + self.client.authentication = authentication + self.client.address = address + ftp = self.client.open_sftp() with ftp.file("/sys/devices/soc0/machine") as file: machine_contents = file.read().decode("utf-8").strip("\n") @@ -150,9 +156,10 @@ def connect_to_device( if remote_address is None: remote_address = self.get_remarkable_address() + self.address = remote_address # For future reference else: if self.check_is_address_reachable(remote_address) is False: - raise SystemExit(f"Error: Device {remote_address} is not reachable!") + raise SystemError(f"Error: Device {remote_address} is not reachable!") client = paramiko.client.SSHClient() client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) @@ -240,9 +247,11 @@ def get_device_status(self) -> tuple[str | None, str, str]: ).group() except Exception: with ftp.file("/etc/os-release") as file: - xochitl_version = re.search( - "(?<=IMG_VERSION=).*", file.read().decode("utf-8") - ).group() + xochitl_version = ( + re.search("(?<=IMG_VERSION=).*", file.read().decode("utf-8")) + .group() + .strip('"') + ) old_update_engine = False with ftp.file("/etc/version") as file: @@ -260,11 +269,13 @@ def get_device_status(self) -> tuple[str | None, str, str]: ).group() else: with open("/etc/os-release") as file: - xochitl_version = re.search( - "(?<=IMG_VERSION=).*", file.read().decode("utf-8") - ).group() - old_update_engine = False + xochitl_version = ( + re.search("(?<=IMG_VERSION=).*", file.read().decode("utf-8")) + .group() + .strip('"') + ) + old_update_engine = False if os.path.exists("/etc/version"): with open("/etc/version") as file: version_id = file.read().rstrip() @@ -402,13 +413,6 @@ def restore_previous_version(self) -> None: self.logger.debug("Restore script ran") - def reboot_device(self) -> None: - """Reboots the device""" - if self.client: - self.client.exec_command("/sbin/reboot") - else: - os.system("reboot") - def install_sw_update(self, version_file: str) -> None: """ Installs new version from version file path, utilising swupdate @@ -427,12 +431,12 @@ def install_sw_update(self, version_file: str) -> None: print(f"Uploading {version_file} image") - out_location = f'/tmp/{version_file.split("/")[-1]}.swu' + out_location = f'/tmp/{os.path.basename(version_file)}.swu' ftp_client.put( version_file, out_location, callback=self.output_put_progress ) - print("\nDone! Running swupdate... (PLEASE BE PATIENT, ~5 MINUTES)") + print("\nDone! Running swupdate (PLEASE BE PATIENT, ~5 MINUTES)") command = command.replace("VERSION_FILE", out_location) @@ -443,6 +447,8 @@ def install_sw_update(self, version_file: str) -> None: self.logger.debug(command) _stdin, stdout, _stderr = self.client.exec_command(command) + self.logger.debug(f"Stdout of swupdate checking: {stdout.readlines()}") + exit_status = stdout.channel.recv_exit_status() if exit_status != 0: @@ -450,9 +456,32 @@ def install_sw_update(self, version_file: str) -> None: continue else: print("".join(_stderr.readlines())) - raise SystemExit("Update failed") + raise SystemError("Update failed!") - self.logger.debug(f"Stdout of swupdate checking: {stdout.readlines()}") + print("Done! Now rebooting the device and disabling update service") + + #### Now disable automatic updates + + self.client.exec_command("sleep 1 && reboot") # Should be enough + self.client.close() + + time.sleep( + 2 + ) # Somehow the code runs faster than the time it takes for the device to reboot + + print("Trying to connect to device") + + while not self.check_is_address_reachable(self.address): + time.sleep(1) + + self.client = self.connect_to_device( + remote_address=self.address, authentication=self.authentication + ) + self.client.exec_command("systemctl stop swupdate memfaultd") + + print( + "Update complete and update service disabled, restart device to enable it" + ) else: print("Running swupdate") @@ -479,14 +508,14 @@ def install_sw_update(self, version_file: str) -> None: continue else: print("".join(process.stderr.readlines())) - raise SystemExit("Update failed") + raise SystemError("Update failed") self.logger.debug( f'Stdout of update checking service is {"".join(process.stdout.readlines())}' ) - print("Success! Rebooting the device...") - self.reboot_device() + print("Update complete and device rebooting") + os.system("reboot") def install_ohma_update(self, version_available: dict) -> None: """Installs version from update folder on the device @@ -526,7 +555,7 @@ def install_ohma_update(self, version_available: dict) -> None: self.logger.debug(f"Stdout of nc checking: {stdout.readlines()}") if check != 0: - raise SystemExit( + raise SystemError( "Device cannot connect to this machine! Is the firewall blocking connections?" ) @@ -541,14 +570,36 @@ def install_ohma_update(self, version_available: dict) -> None: if exit_status != 0: print("".join(_stderr.readlines())) - raise SystemExit("There was an error updating :(") + raise SystemError("There was an error updating :(") self.logger.debug( f'Stdout of update checking service is {"".join(_stderr.readlines())}' ) - print("Success! Rebooting the device...") - self.reboot_device() + #### Now disable automatic updates + + print("Done! Now rebooting the device and disabling update service") + + self.client.exec_command("sleep 1 && reboot") # Should be enough + self.client.close() + + time.sleep( + 2 + ) # Somehow the code runs faster than the time it takes for the device to reboot + + print("Trying to connect to device") + + while not self.check_is_address_reachable(address): + time.sleep(1) + + self.client = self.connect_to_device( + remote_address=address, authentication=authentication + ) + self.client.exec_command("systemctl stop update-engine") + + print( + "Update complete and update service disabled. Restart device to enable it" + ) else: print("Enabling update service") @@ -570,20 +621,20 @@ def install_ohma_update(self, version_available: dict) -> None: if process.wait() != 0: print("".join(process.stderr.readlines())) - raise SystemExit("There was an error updating :(") + raise SystemError("There was an error updating :(") self.logger.debug( f'Stdout of update checking service is {"".join(process.stderr.readlines())}' ) - print("Success! Rebooting the device...") - self.reboot_device() + print("Update complete and device rebooting") + os.system("reboot") @staticmethod def output_put_progress(transferred: int, toBeTransferred: int) -> None: """Used for displaying progress for paramiko ftp.put function""" print( - f"Transferring progress...{int((transferred/toBeTransferred)*100)}%", + f"Transferring progress{int((transferred/toBeTransferred)*100)}%", end="\r", ) diff --git a/codexctl/server.py b/codexctl/server.py index 2c180cd..766d8f7 100644 --- a/codexctl/server.py +++ b/codexctl/server.py @@ -161,7 +161,7 @@ def startUpdate(versionsGiven, host, port=8080): if not available_versions: raise FileNotFoundError("Could not find any update files") - + handler = MySimpleHTTPRequestHandler print(f"Starting fake updater at {host}:{port}") try: diff --git a/codexctl/updates.py b/codexctl/updates.py index 7af3ff8..a149827 100644 --- a/codexctl/updates.py +++ b/codexctl/updates.py @@ -65,7 +65,7 @@ def get_remarkable_versions(self) -> tuple[dict, dict, dict, str, str]: with open(file_location) as f: contents = json.load(f) except ValueError: - raise SystemExit( + raise SystemError( f"Version-ids.json @ {file_location} is corrupted! Please delete it and try again. Also, PLEASE open an issue on the repo showing the contents of the file." ) @@ -82,7 +82,7 @@ def get_remarkable_versions(self) -> tuple[dict, dict, dict, str, str]: return ( contents["remarkablepp"], - ["remarkable2"], + contents["remarkable2"], contents["remarkable1"], contents["external-provider-url"], ) @@ -103,7 +103,7 @@ def update_version_ids(self, location: str) -> None: "https://raw.githubusercontent.com/Jayy001/codexctl/main/data/version-ids.json" ).json() json.dump(contents, f, indent=4) - f.write('\n') + f.write("\n") except requests.exceptions.Timeout: raise SystemExit( "Connection timed out while downloading version-ids.json! Do you have an internet connection?" @@ -149,13 +149,14 @@ def get_toltec_version(self, device_type: str) -> str: response = requests.get("https://toltec-dev.org/stable/Compatibility") if response.status_code != 200: raise SystemExit( - f"Error: Failed to get toltec compatibility table: {response.status_code}") + f"Error: Failed to get toltec compatibility table: {response.status_code}" + ) return self.__max_version( [ x.split("=")[1] for x in response.text.splitlines() - if x.startswith(f"rm{device_type}=") + if x.startswith(f"{device_type}=") ] ) @@ -192,13 +193,15 @@ def download_version( BASE_URL = "https://updates-download.cloud.remarkable.engineering/build/reMarkable%20Device%20Beta/RM110" # Default URL for v2 versions BASE_URL_V3 = "https://updates-download.cloud.remarkable.engineering/build/reMarkable%20Device/reMarkable" - if device_type in ("rm1", "reMarkable 1"): + if device_type in ("rm1", "reMarkable 1", "remarkable1"): version_lookup = self.remarkable1_versions - elif device_type in ("rm2", "reMarkable 2"): + elif device_type in ("rm2", "reMarkable 2", "remarkable2"): version_lookup = self.remarkable2_versions BASE_URL_V3 += "2" - elif device_type in ("rmpp", "reMarkable Ferrari"): + elif device_type in ("rmpp", "rmpro", "reMarkable Ferrari", "ferrari"): version_lookup = self.remarkablepp_versions + else: + raise SystemError("Hardware version does not exist! (rm1,rm2,rmpp)") if update_version not in version_lookup: self.logger.error( @@ -222,7 +225,7 @@ def download_version( file_url = self.external_provider_url.replace("REPLACE_ID", version_id) file_name = f"remarkable-production-memfault-image-{update_version}-{device_type.replace(' ', '-')}-public" else: - file_name = f"{update_version}_reMarkable{'2' if device_type == 'remarkable2' else ''}-{version_id}.signed" + file_name = f"{update_version}_reMarkable{'2' if '2' in device_type else ''}-{version_id}.signed" file_url = f"{BASE_URL}/{update_version}/{file_name}" self.logger.debug(f"File URL is {file_url}, File name is {file_name}") @@ -252,7 +255,9 @@ def __generate_xml_data(self) -> str: -""".format(**params) +""".format( + **params + ) def __parse_response(self, resp: str) -> tuple[str, str, str] | None: """Parses the response from the update server and returns the file name, uri, and version if an update is available @@ -360,4 +365,4 @@ def uses_new_update_engine(version: str) -> bool: @staticmethod def __max_version(versions: list) -> str: """Returns the highest avaliable version from a list with semantic versioning""" - return sorted(versions, key=lambda v: tuple(map(int, v.split("."))))[-1] \ No newline at end of file + return sorted(versions, key=lambda v: tuple(map(int, v.split("."))))[-1] diff --git a/data/version-ids.json b/data/version-ids.json index 763e920..11a5dc5 100644 --- a/data/version-ids.json +++ b/data/version-ids.json @@ -1,31 +1,31 @@ { "remarkable1": { "3.15.3.1": [ - "5WwrMjtr", + "remarkable-production-memfault-image-3.15.3.1-rm1-public.swu", "03a6ff64df69da292ebf82286e361b95d3ddeb1958618fe1c9e302bc6403c7f4" ], "3.14.1.9": [ - "y7ETye1D", + "remarkable-production-memfault-image-3.14.1.9-rm1-public.swu", "d2293e3395bb966708465efbf9c1d28718f8d7f2d14c95bb69c62b0153f07bae" ], "3.13.2.0": [ - "8VN1JWcv", + "remarkable-production-memfault-image-3.13.2.0-rm1-public.swu", "a0be8e1d53ea3b7dc500c8b8564ab21e14e9dbaaec665549787c444cbc428eb9" ], - "3.13.1.1": [ - "uMsYLuQJ", + "3.13.1.2": [ + "remarkable-production-memfault-image-3.13.1.2-rm1-public.swu", "0e7f92c46e355c2e35b45f8fa2bb5a6e3ba1191424da692fb6facac0d5233a6a" ], "3.12.4.4": [ - "eQuyfLac", + "remarkable-production-memfault-image-3.12.4.4-rm1-public.swu", "a735ccc2175625d38a270b523ff9c1c29f801cb662da085c06af23d945152e07" ], "3.12.4.3": [ - "BzKHwLgt", + "remarkable-production-memfault-image-3.12.4.3-rm1-public.swu", "1bd3d76cb773aaa40a1cc3c745a5ed0b3313ad9147316b589853efa4d1a5865d" ], "3.11.3.3": [ - "6YtUhFWd", + "remarkable-production-memfault-image-3.11.3.3-rm1-public.swu", "37c8a4a61a59fdd6e893e75ce48b9f0283e41718b00aeebc3da81f9f7fa1006c" ], "3.11.2.5": [ @@ -155,27 +155,27 @@ }, "remarkable2": { "3.14.1.9": [ - "VxfewUKd", + "remarkable-production-memfault-image-3.14.1.9-rm2-public.swu", "f3394019dbfec3628eec1aee4502d00bd8af1921ed2bcd9edfa01221e445df32" ], "3.13.2.0": [ - "nLpbQPku", + "remarkable-production-memfault-image-3.13.2.0-rm2-public.swu", "331be43421a3a655a5b886c583917341c9912f0aa056d3475b83906dfa9720f5" ], "3.13.1.2": [ - "y87zDd7G", + "remarkable-production-memfault-image-3.13.1.2-rm2-public.swu", "cbd3fc3de77b152aa04e4098b621b52de0f1b053d3652f7d54e14b654711f80c" ], "3.12.4.4": [ - "CDb3wWkA", + "remarkable-production-memfault-image-3.12.4.4-rm2-public.swu", "a15448464e34c868674134b656707823aac305e6b7e1fe1882d3d60182947088" ], "3.12.4.3": [ - "6yGwjeWJ", + "remarkable-production-memfault-image-3.12.4.3-rm2-public.swu", "a1780b93e27f226e03496b41419acd53eb8caf531c4768d7675f2589ae419aaa" ], "3.11.3.3": [ - "JhJKzYPz", + "remarkable-production-memfault-image-3.11.3.3-rm2-public.swu", "cb4a598c575acbab2a4480b13521a97def846337a46b856feb778a190f9d5b47" ], "3.11.2.5": [ @@ -314,21 +314,25 @@ "JLB6Ax3hnJ-", "abde0fac3d12f7599a167414e2871fd340fe10312bc5cb1b65af958b4f5f0736" ] - }, + }, "remarkablepp": { + "3.15.4.2": [ + "remarkable-ct-prototype-image-3.15.4.2-ferrari-public.swu", + "e0db4681888d2294c768906e7a564bc06d936dbb5e9fefc1529cf7a438dae628" + ], "3.14.4.0": [ - "tAVLU9Fb", + "remarkable-ct-prototype-image-3.14.4.0-ferrari-public.swu", "4c93cbc85c061520421c71a4b99ec30ef25e41c077e40d542e068db272900a1e" ], "3.14.3.0": [ - "85LTy2od", + "remarkable-ct-prototype-image-3.14.3.0-ferrari-public.swu", "ad1c28c9031f0b14a6a897b50ccfd402e6c0711d5d129edd8cfa03879d473073" ], "3.14.1.10": [ - "DD3JSHXU", + "remarkable-ct-prototype-image-3.14.1.10-ferrari-public.swu", "8c92f589900e7e355697206c71e2256d909313fcd96aa2c5fd9910ff04b062f1" ] }, "last-updated": 1730730991, - "external-provider-url": "https://pixeldrain.com/api/file/REPLACE_ID?download" + "external-provider-url": "https://storage.googleapis.com/remarkable-versions/REPLACE_ID" } \ No newline at end of file diff --git a/codexctl.py b/main.py similarity index 96% rename from codexctl.py rename to main.py index e354c37..23bb1c9 100644 --- a/codexctl.py +++ b/main.py @@ -9,4 +9,4 @@ from codexctl import main if __name__ == "__main__": - main() \ No newline at end of file + main() diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..650c2a4 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,24 @@ +[tool.poetry] +name = "codexctl" +version = "0.3.0" +description = "Automated update managment for the ReMarkable tablet" +authors = ["Jayy001 "] +license = "GPLv3" +readme = "README.md" + +[tool.poetry.dependencies] +python = "^3.12" +paramiko = "3.4.1" +psutil = "6.0.0" +requests = "2.31.0" +loguru = "0.7.2" +remarkable-update-image = { version = "1.1.3", markers = "sys_platform != 'linux'" } +remarkable-update-fuse = { version = "1.1.2", markers = "sys_platform == 'linux'" } + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" + +[tool.poetry.scripts] +codexctl = "codexctl.__main__:main" +cxtl = "codexctl.__main__:main" \ No newline at end of file diff --git a/tests/test.py b/tests/test.py index d1df2b9..df98b9c 100644 --- a/tests/test.py +++ b/tests/test.py @@ -2,6 +2,10 @@ import sys import difflib import contextlib + +import sys + +sys.path.insert(0, "..") import codexctl from collections import namedtuple @@ -13,6 +17,8 @@ assert os.path.exists(UPDATE_FILE_PATH), "Update image missing" +sys.exit(0) # TODO: Fix tests. + class BufferWriter: def __init__(self, buffer):