codexctl 1.1.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.1.0 → codexctl-1.2.1}/PKG-INFO +33 -17
- {codexctl-1.1.0 → codexctl-1.2.1}/README.md +24 -10
- {codexctl-1.1.0 → codexctl-1.2.1}/codexctl/__init__.py +216 -53
- {codexctl-1.1.0 → codexctl-1.2.1}/codexctl/analysis.py +2 -1
- codexctl-1.2.1/codexctl/device.py +1018 -0
- {codexctl-1.1.0 → codexctl-1.2.1}/codexctl/sync.py +33 -8
- {codexctl-1.1.0 → codexctl-1.2.1}/codexctl/updates.py +124 -60
- {codexctl-1.1.0 → codexctl-1.2.1}/pyproject.toml +6 -6
- codexctl-1.1.0/codexctl/device.py +0 -643
- {codexctl-1.1.0 → codexctl-1.2.1}/LICENSE +0 -0
- {codexctl-1.1.0 → codexctl-1.2.1}/codexctl/__main__.py +0 -0
- {codexctl-1.1.0 → codexctl-1.2.1}/codexctl/server.py +0 -0
|
@@ -1,20 +1,22 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: codexctl
|
|
3
|
-
Version: 1.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
22
|
|
|
@@ -22,11 +24,15 @@ Description-Content-Type: text/markdown
|
|
|
22
24
|
# Codexctl
|
|
23
25
|
A utility program that helps to manage the remarkable device version utilizing [ddvks update server](https://github.com/ddvk/remarkable-update)
|
|
24
26
|
|
|
25
|
-
## 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
|
|
26
28
|
|
|
27
|
-
If your reMarkable device is above 3.11 and you want to downgrade to a version below 3.11, codexctl cannot do this currently. Please refer to #
|
|
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.
|
|
28
30
|
|
|
29
|
-
##
|
|
31
|
+
## Paper Pro bootloader updates
|
|
32
|
+
|
|
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.
|
|
34
|
+
|
|
35
|
+
## Installation
|
|
30
36
|
|
|
31
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.
|
|
32
38
|
|
|
@@ -44,9 +50,9 @@ positional arguments:
|
|
|
44
50
|
install Install the specified version (will download if not available on the device)
|
|
45
51
|
download Download the specified version firmware file
|
|
46
52
|
backup Download remote files to local directory
|
|
47
|
-
cat Cat the contents of a file inside a
|
|
53
|
+
cat Cat the contents of a file inside a firmwareimage
|
|
48
54
|
ls List files inside a firmware image
|
|
49
|
-
extract Extract the specified version
|
|
55
|
+
extract Extract the specified version update file
|
|
50
56
|
mount Mount the specified version firmware filesystem
|
|
51
57
|
upload Upload folder/files to device (pdf only)
|
|
52
58
|
status Get the current version of the device and other information
|
|
@@ -69,18 +75,27 @@ codexctl install latest
|
|
|
69
75
|
```
|
|
70
76
|
- Downloading rmpp version 3.15.4.2 to a folder named `out` and then installing it
|
|
71
77
|
```
|
|
72
|
-
codexctl download 3.15.4.2
|
|
78
|
+
codexctl download 3.15.4.2 --hardware rmpp -o out
|
|
73
79
|
codexctl install ./out/remarkable-ct-prototype-image-3.15.4.2-ferrari-public.swu
|
|
74
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
|
+
```
|
|
75
86
|
- Backing up all documents to the cwd
|
|
76
87
|
```
|
|
77
88
|
codexctl backup
|
|
78
89
|
```
|
|
90
|
+
- Backing up modified/newer documents only, overwrites out-of-date files on disk
|
|
91
|
+
```
|
|
92
|
+
codexctl backup --incremental
|
|
93
|
+
```
|
|
79
94
|
- Backing up only documents in a folder named "FM" to cwd, without overwriting any current files
|
|
80
95
|
```
|
|
81
96
|
codexctl backup -l root -r FM --no-recursion --no-overwrite
|
|
82
97
|
```
|
|
83
|
-
- 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
|
|
84
99
|
```
|
|
85
100
|
codexctl status
|
|
86
101
|
codexctl restore
|
|
@@ -90,7 +105,8 @@ codexctl restore
|
|
|
90
105
|
codexctl download 3.8.0.1944 --hardware rm2
|
|
91
106
|
codexctl cat 3.8.0.1944_reMarkable2-7eGpAv7sYB.signed /etc/version
|
|
92
107
|
```
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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
|
+
```
|
|
96
112
|
|
|
@@ -3,11 +3,15 @@
|
|
|
3
3
|
# Codexctl
|
|
4
4
|
A utility program that helps to manage the remarkable device version utilizing [ddvks update server](https://github.com/ddvk/remarkable-update)
|
|
5
5
|
|
|
6
|
-
## 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
7
|
|
|
8
|
-
If your reMarkable device is above 3.11 and you want to downgrade to a version below 3.11, codexctl cannot do this currently. Please refer to #
|
|
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
9
|
|
|
10
|
-
##
|
|
10
|
+
## Paper Pro bootloader updates
|
|
11
|
+
|
|
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.
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
11
15
|
|
|
12
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.
|
|
13
17
|
|
|
@@ -25,9 +29,9 @@ positional arguments:
|
|
|
25
29
|
install Install the specified version (will download if not available on the device)
|
|
26
30
|
download Download the specified version firmware file
|
|
27
31
|
backup Download remote files to local directory
|
|
28
|
-
cat Cat the contents of a file inside a
|
|
32
|
+
cat Cat the contents of a file inside a firmwareimage
|
|
29
33
|
ls List files inside a firmware image
|
|
30
|
-
extract Extract the specified version
|
|
34
|
+
extract Extract the specified version update file
|
|
31
35
|
mount Mount the specified version firmware filesystem
|
|
32
36
|
upload Upload folder/files to device (pdf only)
|
|
33
37
|
status Get the current version of the device and other information
|
|
@@ -50,18 +54,27 @@ codexctl install latest
|
|
|
50
54
|
```
|
|
51
55
|
- Downloading rmpp version 3.15.4.2 to a folder named `out` and then installing it
|
|
52
56
|
```
|
|
53
|
-
codexctl download 3.15.4.2
|
|
57
|
+
codexctl download 3.15.4.2 --hardware rmpp -o out
|
|
54
58
|
codexctl install ./out/remarkable-ct-prototype-image-3.15.4.2-ferrari-public.swu
|
|
55
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
|
+
```
|
|
56
65
|
- Backing up all documents to the cwd
|
|
57
66
|
```
|
|
58
67
|
codexctl backup
|
|
59
68
|
```
|
|
69
|
+
- Backing up modified/newer documents only, overwrites out-of-date files on disk
|
|
70
|
+
```
|
|
71
|
+
codexctl backup --incremental
|
|
72
|
+
```
|
|
60
73
|
- Backing up only documents in a folder named "FM" to cwd, without overwriting any current files
|
|
61
74
|
```
|
|
62
75
|
codexctl backup -l root -r FM --no-recursion --no-overwrite
|
|
63
76
|
```
|
|
64
|
-
- 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
|
|
65
78
|
```
|
|
66
79
|
codexctl status
|
|
67
80
|
codexctl restore
|
|
@@ -71,6 +84,7 @@ codexctl restore
|
|
|
71
84
|
codexctl download 3.8.0.1944 --hardware rm2
|
|
72
85
|
codexctl cat 3.8.0.1944_reMarkable2-7eGpAv7sYB.signed /etc/version
|
|
73
86
|
```
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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,25 +187,16 @@ 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"])
|
|
167
194
|
|
|
168
|
-
### Transfer & Download functionalities
|
|
169
|
-
elif function in ("transfer", "download"):
|
|
170
|
-
remarkable = DeviceManager(
|
|
171
|
-
remote=remote,
|
|
172
|
-
address=args["address"],
|
|
173
|
-
logger=self.logger,
|
|
174
|
-
authentication=args["password"],
|
|
175
|
-
)
|
|
176
|
-
|
|
177
|
-
|
|
178
195
|
### Update & Version functionalities
|
|
179
196
|
elif function in ("install", "status", "restore"):
|
|
180
197
|
remote = False
|
|
181
198
|
|
|
182
|
-
if "
|
|
199
|
+
if "reMarkable" not in self.device:
|
|
183
200
|
if importlib.util.find_spec("paramiko") is None:
|
|
184
201
|
raise ImportError(
|
|
185
202
|
"Paramiko is required for SSH access. Please install it."
|
|
@@ -190,6 +207,13 @@ class Manager:
|
|
|
190
207
|
)
|
|
191
208
|
remote = True
|
|
192
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
|
+
|
|
193
217
|
from .device import DeviceManager
|
|
194
218
|
from .server import get_available_version
|
|
195
219
|
|
|
@@ -206,17 +230,15 @@ class Manager:
|
|
|
206
230
|
version = self.updater.get_toltec_version(remarkable.hardware)
|
|
207
231
|
|
|
208
232
|
if function == "status":
|
|
209
|
-
beta, prev, current, version_id = remarkable.get_device_status()
|
|
233
|
+
beta, prev, current, version_id, backup = remarkable.get_device_status()
|
|
210
234
|
print(
|
|
211
|
-
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}"
|
|
212
236
|
)
|
|
213
237
|
|
|
214
238
|
elif function == "restore":
|
|
215
|
-
if remarkable.hardware == "ferrari":
|
|
216
|
-
raise SystemError("Restore not available for rmpro.")
|
|
217
239
|
remarkable.restore_previous_version()
|
|
218
240
|
print(
|
|
219
|
-
f"Device restored to previous version [{remarkable.get_device_status()[
|
|
241
|
+
f"Device restored to previous version [{remarkable.get_device_status()[4]}]"
|
|
220
242
|
)
|
|
221
243
|
remarkable.reboot_device()
|
|
222
244
|
print("Device rebooted")
|
|
@@ -229,16 +251,60 @@ class Manager:
|
|
|
229
251
|
# Do we have a specific update file to serve?
|
|
230
252
|
|
|
231
253
|
update_file = version if os.path.isfile(version) else None
|
|
232
|
-
|
|
233
|
-
version_lookup
|
|
234
|
-
|
|
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}")
|
|
235
293
|
|
|
236
294
|
if not version_number:
|
|
237
|
-
|
|
238
|
-
if not
|
|
239
|
-
|
|
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!")
|
|
240
304
|
|
|
241
|
-
|
|
305
|
+
version_number = version_match.group()
|
|
306
|
+
else:
|
|
307
|
+
version_number = str(version_number)
|
|
242
308
|
|
|
243
309
|
update_file_requires_new_engine = UpdateManager.uses_new_update_engine(
|
|
244
310
|
version_number
|
|
@@ -251,23 +317,91 @@ class Manager:
|
|
|
251
317
|
|
|
252
318
|
if device_version_uses_new_engine:
|
|
253
319
|
if not update_file_requires_new_engine:
|
|
254
|
-
raise SystemError(
|
|
320
|
+
raise SystemError(
|
|
321
|
+
"Cannot downgrade to this version as it uses the old update engine, please manually downgrade."
|
|
322
|
+
)
|
|
255
323
|
# TODO: Implement manual downgrading.
|
|
256
324
|
# `codexctl download --out . 3.11.2.5`
|
|
257
325
|
# `codexctl extract --out 3.11.2.5.img 3.11.2.5_reMarkable2-qLFGoqPtPL.signed`
|
|
258
326
|
# `codexctl transfer 3.11.2.5.img ~/root`
|
|
259
327
|
# `dd if=/home/root/3.11.2.5.img of=/dev/mmcblk2p2` (depending on fallback partition)
|
|
260
328
|
# `codexctl restore`
|
|
261
|
-
|
|
329
|
+
|
|
262
330
|
else:
|
|
263
331
|
if update_file_requires_new_engine:
|
|
264
|
-
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
|
+
)
|
|
265
335
|
|
|
266
336
|
#############################################################
|
|
267
337
|
|
|
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()
|
|
399
|
+
|
|
268
400
|
if not update_file_requires_new_engine:
|
|
269
|
-
if update_file:
|
|
270
|
-
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"):
|
|
271
405
|
if not os.path.exists("updates"):
|
|
272
406
|
os.mkdir("updates")
|
|
273
407
|
shutil.move(update_file, "updates")
|
|
@@ -303,7 +437,7 @@ class Manager:
|
|
|
303
437
|
)
|
|
304
438
|
|
|
305
439
|
if device_version_uses_new_engine:
|
|
306
|
-
remarkable.install_sw_update(update_file)
|
|
440
|
+
remarkable.install_sw_update(update_file, bootloader_files=bootloader_files_for_install)
|
|
307
441
|
else:
|
|
308
442
|
remarkable.install_ohma_update(update_file)
|
|
309
443
|
|
|
@@ -328,6 +462,7 @@ def main() -> None:
|
|
|
328
462
|
required=False,
|
|
329
463
|
help="Enable verbose logging",
|
|
330
464
|
action="store_true",
|
|
465
|
+
dest="verbose",
|
|
331
466
|
)
|
|
332
467
|
parser.add_argument(
|
|
333
468
|
"--address",
|
|
@@ -335,12 +470,14 @@ def main() -> None:
|
|
|
335
470
|
required=False,
|
|
336
471
|
help="Specify the address of the device",
|
|
337
472
|
default=None,
|
|
473
|
+
dest="address",
|
|
338
474
|
)
|
|
339
475
|
parser.add_argument(
|
|
340
476
|
"--password",
|
|
341
477
|
"-p",
|
|
342
478
|
required=False,
|
|
343
479
|
help="Specify password or path to SSH key for remote access",
|
|
480
|
+
dest="password",
|
|
344
481
|
)
|
|
345
482
|
subparsers = parser.add_subparsers(dest="command")
|
|
346
483
|
subparsers.required = True # This fixes a bug with older versions of python
|
|
@@ -359,7 +496,12 @@ def main() -> None:
|
|
|
359
496
|
download.add_argument("version", help="Version to download")
|
|
360
497
|
download.add_argument("--out", "-o", help="Folder to download to", default=None)
|
|
361
498
|
download.add_argument(
|
|
362
|
-
"--hardware",
|
|
499
|
+
"--hardware",
|
|
500
|
+
"--device",
|
|
501
|
+
"-d",
|
|
502
|
+
help="Hardware to download for",
|
|
503
|
+
required=True,
|
|
504
|
+
dest="hardware",
|
|
363
505
|
)
|
|
364
506
|
|
|
365
507
|
### Backup subcommand
|
|
@@ -371,21 +513,34 @@ def main() -> None:
|
|
|
371
513
|
"--remote",
|
|
372
514
|
help="Remote directory to backup. Defaults to download folder",
|
|
373
515
|
default="",
|
|
516
|
+
dest="remote",
|
|
374
517
|
)
|
|
375
518
|
backup.add_argument(
|
|
376
519
|
"-l",
|
|
377
520
|
"--local",
|
|
378
521
|
help="Local directory to backup to. Defaults to download folder",
|
|
379
522
|
default="./",
|
|
523
|
+
dest="local",
|
|
380
524
|
)
|
|
381
525
|
backup.add_argument(
|
|
382
|
-
"-
|
|
526
|
+
"-R",
|
|
383
527
|
"--no-recursion",
|
|
384
528
|
help="Disables recursively backup remote directory",
|
|
385
529
|
action="store_true",
|
|
530
|
+
dest="no_recursion",
|
|
386
531
|
)
|
|
387
532
|
backup.add_argument(
|
|
388
|
-
"-
|
|
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",
|
|
389
544
|
)
|
|
390
545
|
|
|
391
546
|
### Cat subcommand
|
|
@@ -405,7 +560,7 @@ def main() -> None:
|
|
|
405
560
|
"extract", help="Extract the specified version update file"
|
|
406
561
|
)
|
|
407
562
|
extract.add_argument("file", help="Path to update file to extract", default=None)
|
|
408
|
-
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")
|
|
409
564
|
|
|
410
565
|
### Mount subcommand
|
|
411
566
|
mount = subparsers.add_parser(
|
|
@@ -430,6 +585,7 @@ def main() -> None:
|
|
|
430
585
|
"--remote",
|
|
431
586
|
help="Remote directory to upload to. Defaults to root folder",
|
|
432
587
|
default="",
|
|
588
|
+
dest="remote",
|
|
433
589
|
)
|
|
434
590
|
|
|
435
591
|
### Status subcommand
|
|
@@ -443,7 +599,14 @@ def main() -> None:
|
|
|
443
599
|
)
|
|
444
600
|
|
|
445
601
|
### List subcommand
|
|
446
|
-
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
|
+
)
|
|
447
610
|
|
|
448
611
|
### Setting logging level
|
|
449
612
|
args = parser.parse_args()
|