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