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