codexctl 1.0.0__tar.gz → 1.2.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {codexctl-1.0.0 → codexctl-1.2.1}/PKG-INFO +34 -19
- {codexctl-1.0.0 → codexctl-1.2.1}/README.md +25 -12
- {codexctl-1.0.0 → codexctl-1.2.1}/codexctl/__init__.py +215 -45
- {codexctl-1.0.0 → codexctl-1.2.1}/codexctl/analysis.py +2 -1
- codexctl-1.2.1/codexctl/device.py +1018 -0
- {codexctl-1.0.0 → codexctl-1.2.1}/codexctl/sync.py +33 -8
- {codexctl-1.0.0 → codexctl-1.2.1}/codexctl/updates.py +124 -60
- {codexctl-1.0.0 → codexctl-1.2.1}/pyproject.toml +6 -6
- codexctl-1.0.0/codexctl/device.py +0 -643
- {codexctl-1.0.0 → codexctl-1.2.1}/LICENSE +0 -0
- {codexctl-1.0.0 → codexctl-1.2.1}/codexctl/__main__.py +0 -0
- {codexctl-1.0.0 → codexctl-1.2.1}/codexctl/server.py +0 -0
|
@@ -1,33 +1,38 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: codexctl
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.2.1
|
|
4
4
|
Summary: Automated update managment for the ReMarkable tablet
|
|
5
5
|
License: GPLv3
|
|
6
|
+
License-File: LICENSE
|
|
6
7
|
Author: Jayy001
|
|
7
8
|
Author-email: github@skii.dev
|
|
8
|
-
Requires-Python: >=3.12,<
|
|
9
|
+
Requires-Python: >=3.12,<3.14
|
|
9
10
|
Classifier: License :: Other/Proprietary License
|
|
10
11
|
Classifier: Programming Language :: Python :: 3
|
|
11
12
|
Classifier: Programming Language :: Python :: 3.12
|
|
12
|
-
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
14
|
+
Requires-Dist: loguru (==0.7.3)
|
|
13
15
|
Requires-Dist: paramiko (==3.4.1)
|
|
14
16
|
Requires-Dist: psutil (==6.0.0)
|
|
15
|
-
Requires-Dist: remarkable-update-fuse (==1.
|
|
16
|
-
Requires-Dist: remarkable-update-image (==1.
|
|
17
|
-
Requires-Dist: requests (==2.
|
|
17
|
+
Requires-Dist: remarkable-update-fuse (==1.3) ; sys_platform == "linux"
|
|
18
|
+
Requires-Dist: remarkable-update-image (==1.3) ; sys_platform != "linux"
|
|
19
|
+
Requires-Dist: requests (==2.32.4)
|
|
18
20
|
Description-Content-Type: text/markdown
|
|
19
21
|
|
|
20
|
-
|
|
21
|
-
<img src="media/demoLocal.gif">
|
|
22
|
+
|
|
22
23
|
|
|
23
24
|
# Codexctl
|
|
24
25
|
A utility program that helps to manage the remarkable device version utilizing [ddvks update server](https://github.com/ddvk/remarkable-update)
|
|
25
26
|
|
|
26
|
-
## Caveat for downgrading to a version
|
|
27
|
+
## Caveat for downgrading from 3.11.2.5 or greater to a version less than 3.11.2.5
|
|
28
|
+
|
|
29
|
+
If your reMarkable device is at or above 3.11.2.5 and you want to downgrade to a version below 3.11.2.5, codexctl cannot do this currently. Please refer to https://github.com/Jayy001/codexctl/issues/95#issuecomment-2305529048 for manual instructions.
|
|
30
|
+
|
|
31
|
+
## Paper Pro bootloader updates
|
|
27
32
|
|
|
28
|
-
|
|
33
|
+
When downgrading a Paper Pro device across the 3.20/3.22 firmware boundary, codexctl automatically handles bootloader updates. It will download the current version's firmware if needed and extract the necessary bootloader files (`update-bootloader.sh` and `imx-boot`) to ensure a safe downgrade.
|
|
29
34
|
|
|
30
|
-
## Installation
|
|
35
|
+
## Installation
|
|
31
36
|
|
|
32
37
|
You can find pre-compiled binaries on the [releases](https://github.com/Jayy001/codexctl/releases/) page. This includes a build for the reMarkable itself, as well as well as builds for linux, macOS, and Windows. Alternatively, you can install directly from pypi with `pip install codexctl`. Codexctl currently only has support for a **command line interfaces** but a graphical interface is soon to come.
|
|
33
38
|
|
|
@@ -45,9 +50,9 @@ positional arguments:
|
|
|
45
50
|
install Install the specified version (will download if not available on the device)
|
|
46
51
|
download Download the specified version firmware file
|
|
47
52
|
backup Download remote files to local directory
|
|
48
|
-
cat Cat the contents of a file inside a
|
|
53
|
+
cat Cat the contents of a file inside a firmwareimage
|
|
49
54
|
ls List files inside a firmware image
|
|
50
|
-
extract Extract the specified version
|
|
55
|
+
extract Extract the specified version update file
|
|
51
56
|
mount Mount the specified version firmware filesystem
|
|
52
57
|
upload Upload folder/files to device (pdf only)
|
|
53
58
|
status Get the current version of the device and other information
|
|
@@ -70,18 +75,27 @@ codexctl install latest
|
|
|
70
75
|
```
|
|
71
76
|
- Downloading rmpp version 3.15.4.2 to a folder named `out` and then installing it
|
|
72
77
|
```
|
|
73
|
-
codexctl download 3.
|
|
78
|
+
codexctl download 3.15.4.2 --hardware rmpp -o out
|
|
74
79
|
codexctl install ./out/remarkable-ct-prototype-image-3.15.4.2-ferrari-public.swu
|
|
75
80
|
```
|
|
81
|
+
- Downloading rmppm version 3.23.0.64 to a folder named `out` and then installing it
|
|
82
|
+
```
|
|
83
|
+
codexctl download 3.23.0.64 --hardware rmppm -o out
|
|
84
|
+
codexctl install ./out/remarkable-production-image-3.23.0.64-chiappa-public.swu
|
|
85
|
+
```
|
|
76
86
|
- Backing up all documents to the cwd
|
|
77
87
|
```
|
|
78
88
|
codexctl backup
|
|
79
89
|
```
|
|
90
|
+
- Backing up modified/newer documents only, overwrites out-of-date files on disk
|
|
91
|
+
```
|
|
92
|
+
codexctl backup --incremental
|
|
93
|
+
```
|
|
80
94
|
- Backing up only documents in a folder named "FM" to cwd, without overwriting any current files
|
|
81
95
|
```
|
|
82
96
|
codexctl backup -l root -r FM --no-recursion --no-overwrite
|
|
83
97
|
```
|
|
84
|
-
- Getting the version of the device and then switching to previous version
|
|
98
|
+
- Getting the version of the device and then switching to previous version
|
|
85
99
|
```
|
|
86
100
|
codexctl status
|
|
87
101
|
codexctl restore
|
|
@@ -91,7 +105,8 @@ codexctl restore
|
|
|
91
105
|
codexctl download 3.8.0.1944 --hardware rm2
|
|
92
106
|
codexctl cat 3.8.0.1944_reMarkable2-7eGpAv7sYB.signed /etc/version
|
|
93
107
|
```
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
108
|
+
- Extract the filesystem image of an upgrade file as a file named `extracted`
|
|
109
|
+
```
|
|
110
|
+
codexctl extract remarkable-production-image-3.22.0.64-ferrari-public.swu -o extracted
|
|
111
|
+
```
|
|
97
112
|
|
|
@@ -1,14 +1,17 @@
|
|
|
1
|
-
|
|
2
|
-
<img src="media/demoLocal.gif">
|
|
1
|
+
|
|
3
2
|
|
|
4
3
|
# Codexctl
|
|
5
4
|
A utility program that helps to manage the remarkable device version utilizing [ddvks update server](https://github.com/ddvk/remarkable-update)
|
|
6
5
|
|
|
7
|
-
## Caveat for downgrading to a version
|
|
6
|
+
## Caveat for downgrading from 3.11.2.5 or greater to a version less than 3.11.2.5
|
|
7
|
+
|
|
8
|
+
If your reMarkable device is at or above 3.11.2.5 and you want to downgrade to a version below 3.11.2.5, codexctl cannot do this currently. Please refer to https://github.com/Jayy001/codexctl/issues/95#issuecomment-2305529048 for manual instructions.
|
|
9
|
+
|
|
10
|
+
## Paper Pro bootloader updates
|
|
8
11
|
|
|
9
|
-
|
|
12
|
+
When downgrading a Paper Pro device across the 3.20/3.22 firmware boundary, codexctl automatically handles bootloader updates. It will download the current version's firmware if needed and extract the necessary bootloader files (`update-bootloader.sh` and `imx-boot`) to ensure a safe downgrade.
|
|
10
13
|
|
|
11
|
-
## Installation
|
|
14
|
+
## Installation
|
|
12
15
|
|
|
13
16
|
You can find pre-compiled binaries on the [releases](https://github.com/Jayy001/codexctl/releases/) page. This includes a build for the reMarkable itself, as well as well as builds for linux, macOS, and Windows. Alternatively, you can install directly from pypi with `pip install codexctl`. Codexctl currently only has support for a **command line interfaces** but a graphical interface is soon to come.
|
|
14
17
|
|
|
@@ -26,9 +29,9 @@ positional arguments:
|
|
|
26
29
|
install Install the specified version (will download if not available on the device)
|
|
27
30
|
download Download the specified version firmware file
|
|
28
31
|
backup Download remote files to local directory
|
|
29
|
-
cat Cat the contents of a file inside a
|
|
32
|
+
cat Cat the contents of a file inside a firmwareimage
|
|
30
33
|
ls List files inside a firmware image
|
|
31
|
-
extract Extract the specified version
|
|
34
|
+
extract Extract the specified version update file
|
|
32
35
|
mount Mount the specified version firmware filesystem
|
|
33
36
|
upload Upload folder/files to device (pdf only)
|
|
34
37
|
status Get the current version of the device and other information
|
|
@@ -51,18 +54,27 @@ codexctl install latest
|
|
|
51
54
|
```
|
|
52
55
|
- Downloading rmpp version 3.15.4.2 to a folder named `out` and then installing it
|
|
53
56
|
```
|
|
54
|
-
codexctl download 3.
|
|
57
|
+
codexctl download 3.15.4.2 --hardware rmpp -o out
|
|
55
58
|
codexctl install ./out/remarkable-ct-prototype-image-3.15.4.2-ferrari-public.swu
|
|
56
59
|
```
|
|
60
|
+
- Downloading rmppm version 3.23.0.64 to a folder named `out` and then installing it
|
|
61
|
+
```
|
|
62
|
+
codexctl download 3.23.0.64 --hardware rmppm -o out
|
|
63
|
+
codexctl install ./out/remarkable-production-image-3.23.0.64-chiappa-public.swu
|
|
64
|
+
```
|
|
57
65
|
- Backing up all documents to the cwd
|
|
58
66
|
```
|
|
59
67
|
codexctl backup
|
|
60
68
|
```
|
|
69
|
+
- Backing up modified/newer documents only, overwrites out-of-date files on disk
|
|
70
|
+
```
|
|
71
|
+
codexctl backup --incremental
|
|
72
|
+
```
|
|
61
73
|
- Backing up only documents in a folder named "FM" to cwd, without overwriting any current files
|
|
62
74
|
```
|
|
63
75
|
codexctl backup -l root -r FM --no-recursion --no-overwrite
|
|
64
76
|
```
|
|
65
|
-
- Getting the version of the device and then switching to previous version
|
|
77
|
+
- Getting the version of the device and then switching to previous version
|
|
66
78
|
```
|
|
67
79
|
codexctl status
|
|
68
80
|
codexctl restore
|
|
@@ -72,6 +84,7 @@ codexctl restore
|
|
|
72
84
|
codexctl download 3.8.0.1944 --hardware rm2
|
|
73
85
|
codexctl cat 3.8.0.1944_reMarkable2-7eGpAv7sYB.signed /etc/version
|
|
74
86
|
```
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
87
|
+
- Extract the filesystem image of an upgrade file as a file named `extracted`
|
|
88
|
+
```
|
|
89
|
+
codexctl extract remarkable-production-image-3.22.0.64-ferrari-public.swu -o extracted
|
|
90
|
+
```
|
|
@@ -10,6 +10,7 @@ import shutil
|
|
|
10
10
|
import json
|
|
11
11
|
import re
|
|
12
12
|
|
|
13
|
+
from typing import cast
|
|
13
14
|
from os import listdir
|
|
14
15
|
|
|
15
16
|
try:
|
|
@@ -22,6 +23,7 @@ if importlib.util.find_spec("requests") is None:
|
|
|
22
23
|
"Requests is required for accessing remote files. Please install it."
|
|
23
24
|
)
|
|
24
25
|
|
|
26
|
+
from .device import HardwareType
|
|
25
27
|
from .updates import UpdateManager
|
|
26
28
|
|
|
27
29
|
|
|
@@ -49,12 +51,13 @@ class Manager:
|
|
|
49
51
|
args: What arguments to pass into the function
|
|
50
52
|
"""
|
|
51
53
|
|
|
52
|
-
|
|
53
|
-
remarkable_version =
|
|
54
|
-
|
|
55
|
-
|
|
54
|
+
try:
|
|
55
|
+
remarkable_version = HardwareType.parse(self.device)
|
|
56
|
+
except ValueError:
|
|
57
|
+
hw = args.get("hardware")
|
|
58
|
+
remarkable_version = HardwareType.parse(hw) if hw else None
|
|
56
59
|
|
|
57
|
-
version = args.get("version", None)
|
|
60
|
+
version = cast(str | None, args.get("version", None))
|
|
58
61
|
|
|
59
62
|
if remarkable_version:
|
|
60
63
|
if version == "latest":
|
|
@@ -64,16 +67,30 @@ class Manager:
|
|
|
64
67
|
|
|
65
68
|
### Download functionalities
|
|
66
69
|
if function == "list":
|
|
67
|
-
remarkable_pp_versions = "\n".join(
|
|
70
|
+
remarkable_pp_versions = "\n".join(
|
|
71
|
+
self.updater.remarkablepp_versions.keys()
|
|
72
|
+
)
|
|
73
|
+
remarkable_ppm_versions = "\n".join(
|
|
74
|
+
self.updater.remarkableppm_versions.keys()
|
|
75
|
+
)
|
|
68
76
|
remarkable_2_versions = "\n".join(self.updater.remarkable2_versions.keys())
|
|
69
77
|
remarkable_1_versions = "\n".join(self.updater.remarkable1_versions.keys())
|
|
70
78
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
79
|
+
version_blocks = []
|
|
80
|
+
if remarkable_version is None or remarkable_version == HardwareType.RMPP:
|
|
81
|
+
version_blocks.append(f"ReMarkable Paper Pro:\n{remarkable_pp_versions}")
|
|
82
|
+
if remarkable_version is None or remarkable_version == HardwareType.RMPPM:
|
|
83
|
+
version_blocks.append(f"ReMarkable Paper Pro Move:\n{remarkable_ppm_versions}")
|
|
84
|
+
if remarkable_version is None or remarkable_version == HardwareType.RM2:
|
|
85
|
+
version_blocks.append(f"ReMarkable 2:\n{remarkable_2_versions}")
|
|
86
|
+
if remarkable_version is None or remarkable_version == HardwareType.RM1:
|
|
87
|
+
version_blocks.append(f"ReMarkable 1:\n{remarkable_1_versions}")
|
|
88
|
+
|
|
89
|
+
print("\n\n".join(version_blocks))
|
|
74
90
|
|
|
75
91
|
elif function == "download":
|
|
76
92
|
logger.debug(f"Downloading version {version}")
|
|
93
|
+
assert remarkable_version is not None
|
|
77
94
|
filename = self.updater.download_version(
|
|
78
95
|
remarkable_version, version, args["out"]
|
|
79
96
|
)
|
|
@@ -83,24 +100,35 @@ class Manager:
|
|
|
83
100
|
|
|
84
101
|
### Mounting functionalities
|
|
85
102
|
elif function in ("extract", "mount"):
|
|
86
|
-
try:
|
|
87
|
-
from .analysis import get_update_image
|
|
88
|
-
except ImportError:
|
|
89
|
-
raise ImportError(
|
|
90
|
-
"remarkable_update_image is required for analysis. Please install it!"
|
|
91
|
-
)
|
|
92
|
-
|
|
93
103
|
if function == "extract":
|
|
94
104
|
if not args["out"]:
|
|
95
105
|
args["out"] = os.getcwd() + "/extracted"
|
|
96
106
|
|
|
97
107
|
logger.debug(f"Extracting {args['file']} to {args['out']}")
|
|
98
|
-
|
|
108
|
+
|
|
109
|
+
try:
|
|
110
|
+
from remarkable_update_image import UpdateImage
|
|
111
|
+
except ImportError:
|
|
112
|
+
raise ImportError(
|
|
113
|
+
"remarkable_update_image is required for extraction. Please install it!"
|
|
114
|
+
) from None
|
|
115
|
+
|
|
116
|
+
image = UpdateImage(args["file"])
|
|
99
117
|
image.seek(0)
|
|
100
118
|
|
|
101
119
|
with open(args["out"], "wb") as f:
|
|
102
120
|
f.write(image.read())
|
|
121
|
+
|
|
122
|
+
logger.info(f"Extracted image to {args['out']}")
|
|
103
123
|
else:
|
|
124
|
+
try:
|
|
125
|
+
from .analysis import get_update_image
|
|
126
|
+
from remarkable_update_fuse import UpdateFS
|
|
127
|
+
except ImportError:
|
|
128
|
+
raise ImportError(
|
|
129
|
+
"remarkable_update_fuse is required for mounting. Please install it!"
|
|
130
|
+
)
|
|
131
|
+
|
|
104
132
|
if args["out"] is None:
|
|
105
133
|
args["out"] = "/opt/remarkable/"
|
|
106
134
|
|
|
@@ -110,8 +138,6 @@ class Manager:
|
|
|
110
138
|
if not os.path.exists(args["filesystem"]):
|
|
111
139
|
raise SystemExit("Firmware file does not exist!")
|
|
112
140
|
|
|
113
|
-
from remarkable_update_fuse import UpdateFS
|
|
114
|
-
|
|
115
141
|
server = UpdateFS()
|
|
116
142
|
server.parse(
|
|
117
143
|
args=[args["filesystem"], args["out"]], values=server, errex=1
|
|
@@ -161,6 +187,7 @@ class Manager:
|
|
|
161
187
|
remoteFolder=args["remote"],
|
|
162
188
|
recursive=not args["no_recursion"],
|
|
163
189
|
overwrite=not args["no_overwrite"],
|
|
190
|
+
incremental=args["incremental"],
|
|
164
191
|
)
|
|
165
192
|
else:
|
|
166
193
|
rmWeb.upload(input_paths=args["paths"], remoteFolder=args["remote"])
|
|
@@ -169,7 +196,7 @@ class Manager:
|
|
|
169
196
|
elif function in ("install", "status", "restore"):
|
|
170
197
|
remote = False
|
|
171
198
|
|
|
172
|
-
if "
|
|
199
|
+
if "reMarkable" not in self.device:
|
|
173
200
|
if importlib.util.find_spec("paramiko") is None:
|
|
174
201
|
raise ImportError(
|
|
175
202
|
"Paramiko is required for SSH access. Please install it."
|
|
@@ -180,6 +207,13 @@ class Manager:
|
|
|
180
207
|
)
|
|
181
208
|
remote = True
|
|
182
209
|
|
|
210
|
+
try:
|
|
211
|
+
from remarkable_update_image import UpdateImage
|
|
212
|
+
except ImportError:
|
|
213
|
+
raise ImportError(
|
|
214
|
+
"remarkable_update_image is required for install. Please install it!"
|
|
215
|
+
) from None
|
|
216
|
+
|
|
183
217
|
from .device import DeviceManager
|
|
184
218
|
from .server import get_available_version
|
|
185
219
|
|
|
@@ -196,17 +230,15 @@ class Manager:
|
|
|
196
230
|
version = self.updater.get_toltec_version(remarkable.hardware)
|
|
197
231
|
|
|
198
232
|
if function == "status":
|
|
199
|
-
beta, prev, current, version_id = remarkable.get_device_status()
|
|
233
|
+
beta, prev, current, version_id, backup = remarkable.get_device_status()
|
|
200
234
|
print(
|
|
201
|
-
f"\nCurrent version: {current}\nOld update engine: {prev}\nBeta active: {beta}\nVersion id: {version_id}"
|
|
235
|
+
f"\nCurrent version: {current}\nBackup version: {backup}\nOld update engine: {prev}\nBeta active: {beta}\nVersion id: {version_id}"
|
|
202
236
|
)
|
|
203
237
|
|
|
204
238
|
elif function == "restore":
|
|
205
|
-
if remarkable.hardware == "ferrari":
|
|
206
|
-
raise SystemError("Restore not available for rmpro.")
|
|
207
239
|
remarkable.restore_previous_version()
|
|
208
240
|
print(
|
|
209
|
-
f"Device restored to previous version [{remarkable.get_device_status()[
|
|
241
|
+
f"Device restored to previous version [{remarkable.get_device_status()[4]}]"
|
|
210
242
|
)
|
|
211
243
|
remarkable.reboot_device()
|
|
212
244
|
print("Device rebooted")
|
|
@@ -219,16 +251,60 @@ class Manager:
|
|
|
219
251
|
# Do we have a specific update file to serve?
|
|
220
252
|
|
|
221
253
|
update_file = version if os.path.isfile(version) else None
|
|
222
|
-
|
|
223
|
-
version_lookup
|
|
224
|
-
|
|
254
|
+
|
|
255
|
+
def version_lookup(version: str | None) -> re.Match[str] | None:
|
|
256
|
+
return re.search(r"\b\d+\.\d+\.\d+\.\d+\b", cast(str, version))
|
|
257
|
+
|
|
258
|
+
version_number = None
|
|
259
|
+
swu_hardware = None
|
|
260
|
+
|
|
261
|
+
if update_file:
|
|
262
|
+
try:
|
|
263
|
+
from remarkable_update_image import UpdateImage
|
|
264
|
+
from remarkable_update_image.image import CPIOUpdateImage
|
|
265
|
+
|
|
266
|
+
image = UpdateImage(update_file)
|
|
267
|
+
if isinstance(image, CPIOUpdateImage):
|
|
268
|
+
if image.version is None:
|
|
269
|
+
raise SystemError(f"Could not determine version from SWU file: {update_file}")
|
|
270
|
+
|
|
271
|
+
version_number = image.version
|
|
272
|
+
hw_map = {
|
|
273
|
+
"reMarkable1": HardwareType.RM1,
|
|
274
|
+
"reMarkable2": HardwareType.RM2,
|
|
275
|
+
"ferrari": HardwareType.RMPP,
|
|
276
|
+
"chiappa": HardwareType.RMPPM,
|
|
277
|
+
}
|
|
278
|
+
if image.hardware_type not in hw_map:
|
|
279
|
+
raise SystemError(f"Unsupported hardware type in SWU file: {update_file}")
|
|
280
|
+
|
|
281
|
+
swu_hardware = hw_map[image.hardware_type]
|
|
282
|
+
logger.info(f"Extracted from SWU: version={version_number}, hardware={swu_hardware.name}")
|
|
283
|
+
|
|
284
|
+
if swu_hardware != remarkable.hardware:
|
|
285
|
+
raise SystemError(
|
|
286
|
+
f"Hardware mismatch!\n"
|
|
287
|
+
f"SWU file is for: {swu_hardware.name}\n"
|
|
288
|
+
f"Connected device is: {remarkable.hardware.name}\n"
|
|
289
|
+
f"Cannot install firmware for different hardware."
|
|
290
|
+
)
|
|
291
|
+
except ValueError as e:
|
|
292
|
+
logger.warning(f"Could not extract metadata from update file: {e}")
|
|
225
293
|
|
|
226
294
|
if not version_number:
|
|
227
|
-
|
|
228
|
-
if not
|
|
229
|
-
|
|
295
|
+
version_match = version_lookup(version)
|
|
296
|
+
if not version_match:
|
|
297
|
+
version_match = version_lookup(
|
|
298
|
+
input(
|
|
299
|
+
"Failed to get the version number from the filename, please enter it: "
|
|
300
|
+
)
|
|
301
|
+
)
|
|
302
|
+
if not version_match:
|
|
303
|
+
raise SystemError("Invalid version!")
|
|
230
304
|
|
|
231
|
-
|
|
305
|
+
version_number = version_match.group()
|
|
306
|
+
else:
|
|
307
|
+
version_number = str(version_number)
|
|
232
308
|
|
|
233
309
|
update_file_requires_new_engine = UpdateManager.uses_new_update_engine(
|
|
234
310
|
version_number
|
|
@@ -241,26 +317,91 @@ class Manager:
|
|
|
241
317
|
|
|
242
318
|
if device_version_uses_new_engine:
|
|
243
319
|
if not update_file_requires_new_engine:
|
|
244
|
-
raise SystemError(
|
|
320
|
+
raise SystemError(
|
|
321
|
+
"Cannot downgrade to this version as it uses the old update engine, please manually downgrade."
|
|
322
|
+
)
|
|
245
323
|
# TODO: Implement manual downgrading.
|
|
246
324
|
# `codexctl download --out . 3.11.2.5`
|
|
247
325
|
# `codexctl extract --out 3.11.2.5.img 3.11.2.5_reMarkable2-qLFGoqPtPL.signed`
|
|
248
326
|
# `codexctl transfer 3.11.2.5.img ~/root`
|
|
249
327
|
# `dd if=/home/root/3.11.2.5.img of=/dev/mmcblk2p2` (depending on fallback partition)
|
|
250
328
|
# `codexctl restore`
|
|
251
|
-
|
|
329
|
+
|
|
252
330
|
else:
|
|
253
331
|
if update_file_requires_new_engine:
|
|
254
|
-
raise SystemError(
|
|
332
|
+
raise SystemError(
|
|
333
|
+
"This version requires the new update engine, please upgrade your device to version 3.11.2.5 first."
|
|
334
|
+
)
|
|
255
335
|
|
|
256
336
|
#############################################################
|
|
257
337
|
|
|
258
|
-
|
|
259
|
-
|
|
338
|
+
bootloader_files_for_install = None
|
|
339
|
+
|
|
340
|
+
if (device_version_uses_new_engine and
|
|
341
|
+
remarkable.hardware == HardwareType.RMPP):
|
|
342
|
+
|
|
343
|
+
current_version = remarkable.get_device_status()[2]
|
|
344
|
+
|
|
345
|
+
if UpdateManager.is_bootloader_boundary_downgrade(current_version, version_number):
|
|
346
|
+
print("\n" + "="*60)
|
|
347
|
+
print("WARNING: Bootloader Update Required")
|
|
348
|
+
print("="*60)
|
|
349
|
+
print(f"Current version: {current_version}")
|
|
350
|
+
print(f"Target version: {version_number}")
|
|
351
|
+
print()
|
|
352
|
+
print("Downgrading from 3.22+ to <3.22 requires updating the")
|
|
353
|
+
print("bootloader on both partitions. This process will:")
|
|
354
|
+
print(" 1. Download the current version's bootloader files")
|
|
355
|
+
print(" 2. Download the target OS version")
|
|
356
|
+
print(" 3. Install the target OS version")
|
|
357
|
+
print(" 4. Update bootloader on both partitions")
|
|
358
|
+
print(" 5. Reboot")
|
|
359
|
+
print()
|
|
360
|
+
|
|
361
|
+
response = input("Do you want to continue? (y/N): ")
|
|
362
|
+
if response.lower() != 'y':
|
|
363
|
+
raise SystemExit("Installation cancelled by user")
|
|
364
|
+
|
|
365
|
+
expected_swu_name = f"remarkable-production-memfault-image-{current_version}-{remarkable.hardware.new_download_hw}-public"
|
|
366
|
+
expected_swu_path = f"./{expected_swu_name}"
|
|
367
|
+
|
|
368
|
+
if os.path.isfile(expected_swu_path):
|
|
369
|
+
print(f"\nUsing existing {expected_swu_name} for bootloader extraction...")
|
|
370
|
+
current_swu_path = expected_swu_path
|
|
371
|
+
else:
|
|
372
|
+
print("\nDownloading current version's SWU for bootloader extraction...")
|
|
373
|
+
current_swu_path = self.updater.download_version(
|
|
374
|
+
remarkable.hardware,
|
|
375
|
+
current_version,
|
|
376
|
+
"./"
|
|
377
|
+
)
|
|
378
|
+
|
|
379
|
+
if not current_swu_path:
|
|
380
|
+
raise SystemError(
|
|
381
|
+
f"Failed to download current version {current_version} for bootloader extraction. "
|
|
382
|
+
f"This is required for safe downgrade across bootloader boundary."
|
|
383
|
+
)
|
|
384
|
+
|
|
385
|
+
print("Extracting bootloader files...")
|
|
386
|
+
from remarkable_update_image import UpdateImage
|
|
387
|
+
swu_image = UpdateImage(current_swu_path)
|
|
388
|
+
bootloader_files_for_install = {
|
|
389
|
+
'update-bootloader.sh': swu_image.archive[b'update-bootloader.sh'].read(),
|
|
390
|
+
'imx-boot': swu_image.archive[b'imx-boot'].read(),
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
if not all(bootloader_files_for_install.values()):
|
|
394
|
+
raise SystemError("Failed to extract bootloader files from current version")
|
|
395
|
+
|
|
396
|
+
print(f"✓ Extracted update-bootloader.sh ({len(bootloader_files_for_install['update-bootloader.sh'])} bytes)")
|
|
397
|
+
print(f"✓ Extracted imx-boot ({len(bootloader_files_for_install['imx-boot'])} bytes)")
|
|
398
|
+
print()
|
|
260
399
|
|
|
261
400
|
if not update_file_requires_new_engine:
|
|
262
|
-
if update_file:
|
|
263
|
-
if
|
|
401
|
+
if update_file: # Check if file exists
|
|
402
|
+
if os.path.dirname(
|
|
403
|
+
os.path.abspath(update_file)
|
|
404
|
+
) != os.path.abspath("updates"):
|
|
264
405
|
if not os.path.exists("updates"):
|
|
265
406
|
os.mkdir("updates")
|
|
266
407
|
shutil.move(update_file, "updates")
|
|
@@ -296,7 +437,7 @@ class Manager:
|
|
|
296
437
|
)
|
|
297
438
|
|
|
298
439
|
if device_version_uses_new_engine:
|
|
299
|
-
remarkable.install_sw_update(update_file)
|
|
440
|
+
remarkable.install_sw_update(update_file, bootloader_files=bootloader_files_for_install)
|
|
300
441
|
else:
|
|
301
442
|
remarkable.install_ohma_update(update_file)
|
|
302
443
|
|
|
@@ -321,6 +462,7 @@ def main() -> None:
|
|
|
321
462
|
required=False,
|
|
322
463
|
help="Enable verbose logging",
|
|
323
464
|
action="store_true",
|
|
465
|
+
dest="verbose",
|
|
324
466
|
)
|
|
325
467
|
parser.add_argument(
|
|
326
468
|
"--address",
|
|
@@ -328,12 +470,14 @@ def main() -> None:
|
|
|
328
470
|
required=False,
|
|
329
471
|
help="Specify the address of the device",
|
|
330
472
|
default=None,
|
|
473
|
+
dest="address",
|
|
331
474
|
)
|
|
332
475
|
parser.add_argument(
|
|
333
476
|
"--password",
|
|
334
477
|
"-p",
|
|
335
478
|
required=False,
|
|
336
479
|
help="Specify password or path to SSH key for remote access",
|
|
480
|
+
dest="password",
|
|
337
481
|
)
|
|
338
482
|
subparsers = parser.add_subparsers(dest="command")
|
|
339
483
|
subparsers.required = True # This fixes a bug with older versions of python
|
|
@@ -352,7 +496,12 @@ def main() -> None:
|
|
|
352
496
|
download.add_argument("version", help="Version to download")
|
|
353
497
|
download.add_argument("--out", "-o", help="Folder to download to", default=None)
|
|
354
498
|
download.add_argument(
|
|
355
|
-
"--hardware",
|
|
499
|
+
"--hardware",
|
|
500
|
+
"--device",
|
|
501
|
+
"-d",
|
|
502
|
+
help="Hardware to download for",
|
|
503
|
+
required=True,
|
|
504
|
+
dest="hardware",
|
|
356
505
|
)
|
|
357
506
|
|
|
358
507
|
### Backup subcommand
|
|
@@ -364,21 +513,34 @@ def main() -> None:
|
|
|
364
513
|
"--remote",
|
|
365
514
|
help="Remote directory to backup. Defaults to download folder",
|
|
366
515
|
default="",
|
|
516
|
+
dest="remote",
|
|
367
517
|
)
|
|
368
518
|
backup.add_argument(
|
|
369
519
|
"-l",
|
|
370
520
|
"--local",
|
|
371
521
|
help="Local directory to backup to. Defaults to download folder",
|
|
372
522
|
default="./",
|
|
523
|
+
dest="local",
|
|
373
524
|
)
|
|
374
525
|
backup.add_argument(
|
|
375
|
-
"-
|
|
526
|
+
"-R",
|
|
376
527
|
"--no-recursion",
|
|
377
528
|
help="Disables recursively backup remote directory",
|
|
378
529
|
action="store_true",
|
|
530
|
+
dest="no_recursion",
|
|
379
531
|
)
|
|
380
532
|
backup.add_argument(
|
|
381
|
-
"-
|
|
533
|
+
"-O",
|
|
534
|
+
"--no-overwrite",
|
|
535
|
+
help="Disables overwrite",
|
|
536
|
+
action="store_true",
|
|
537
|
+
dest="no_overwrite",
|
|
538
|
+
)
|
|
539
|
+
backup.add_argument(
|
|
540
|
+
"-i",
|
|
541
|
+
"--incremental",
|
|
542
|
+
help="Overwrite out-of-date files only",
|
|
543
|
+
action="store_true",
|
|
382
544
|
)
|
|
383
545
|
|
|
384
546
|
### Cat subcommand
|
|
@@ -398,7 +560,7 @@ def main() -> None:
|
|
|
398
560
|
"extract", help="Extract the specified version update file"
|
|
399
561
|
)
|
|
400
562
|
extract.add_argument("file", help="Path to update file to extract", default=None)
|
|
401
|
-
extract.add_argument("--out", help="Folder to extract to", default=None)
|
|
563
|
+
extract.add_argument("--out", help="Folder to extract to", default=None, dest="out")
|
|
402
564
|
|
|
403
565
|
### Mount subcommand
|
|
404
566
|
mount = subparsers.add_parser(
|
|
@@ -423,6 +585,7 @@ def main() -> None:
|
|
|
423
585
|
"--remote",
|
|
424
586
|
help="Remote directory to upload to. Defaults to root folder",
|
|
425
587
|
default="",
|
|
588
|
+
dest="remote",
|
|
426
589
|
)
|
|
427
590
|
|
|
428
591
|
### Status subcommand
|
|
@@ -436,7 +599,14 @@ def main() -> None:
|
|
|
436
599
|
)
|
|
437
600
|
|
|
438
601
|
### List subcommand
|
|
439
|
-
subparsers.add_parser("list", help="List all available versions")
|
|
602
|
+
list_ = subparsers.add_parser("list", help="List all available versions")
|
|
603
|
+
list_.add_argument(
|
|
604
|
+
"--hardware",
|
|
605
|
+
"--device",
|
|
606
|
+
"-d",
|
|
607
|
+
help="Hardware to list for",
|
|
608
|
+
dest="hardware",
|
|
609
|
+
)
|
|
440
610
|
|
|
441
611
|
### Setting logging level
|
|
442
612
|
args = parser.parse_args()
|