pamoja-update 0.1.18__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.
- pamoja_update-0.1.18/.gitignore +13 -0
- pamoja_update-0.1.18/LICENSE-MIT +21 -0
- pamoja_update-0.1.18/PKG-INFO +127 -0
- pamoja_update-0.1.18/README.md +108 -0
- pamoja_update-0.1.18/pamoja/update/__init__.py +126 -0
- pamoja_update-0.1.18/pamoja/update/py.typed +0 -0
- pamoja_update-0.1.18/pyproject.toml +31 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Anthony Wiedman
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: pamoja-update
|
|
3
|
+
Version: 0.1.18
|
|
4
|
+
Summary: Signed firmware manifests, streaming image verification, and A/B slots that fall back on their own.
|
|
5
|
+
Project-URL: Repository, https://github.com/molexxxx/pamoja
|
|
6
|
+
Project-URL: Documentation, https://pamoja.molex.cloud/docs/guides/update.html
|
|
7
|
+
Author: molexxxx
|
|
8
|
+
License: MIT
|
|
9
|
+
License-File: LICENSE-MIT
|
|
10
|
+
Keywords: iot,pamoja,robotics,update
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Typing :: Typed
|
|
15
|
+
Requires-Python: >=3.10
|
|
16
|
+
Requires-Dist: pamoja-native==0.1.18
|
|
17
|
+
Requires-Dist: pamoja-security==0.1.18
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
|
|
20
|
+
# pamoja-update
|
|
21
|
+
|
|
22
|
+
Signed firmware manifests, streaming image verification, and A/B slots that fall back on their own. One capability of [pamoja](https://github.com/molexxxx/pamoja), one memory-safe Rust core with bindings for TypeScript, Python, and C#.
|
|
23
|
+
|
|
24
|
+
[](https://pamoja.molex.cloud/docs/guides/update.html)
|
|
25
|
+
[](https://pamoja.molex.cloud/docs/)
|
|
26
|
+
[](https://pamoja.molex.cloud/docs/reference/python/pamoja/update.html)
|
|
27
|
+
|
|
28
|
+
## Install
|
|
29
|
+
|
|
30
|
+
```sh
|
|
31
|
+
pip install pamoja-update
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
```python
|
|
35
|
+
from pamoja import update
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
This pulls in `pamoja-native`, the compiled engine, and `pamoja-security`. `pip install pamoja` is the whole framework in one package.
|
|
39
|
+
|
|
40
|
+
## Example
|
|
41
|
+
|
|
42
|
+
The script the test suite runs, spliced here as it ran.
|
|
43
|
+
|
|
44
|
+
From [`bindings/python/guides/update.py`](https://github.com/molexxxx/pamoja/blob/main/bindings/python/guides/update.py):
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
from pamoja.core import PamojaError
|
|
48
|
+
from pamoja.security import DeviceIdentity
|
|
49
|
+
from pamoja.update import (
|
|
50
|
+
BootAction,
|
|
51
|
+
Manifest,
|
|
52
|
+
SlotState,
|
|
53
|
+
Updater,
|
|
54
|
+
image_digest,
|
|
55
|
+
sign_manifest,
|
|
56
|
+
verify_envelope,
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
# The publisher's key signs releases; devices in the field are anchored to its public half
|
|
60
|
+
# and will take firmware from nobody else.
|
|
61
|
+
publisher = DeviceIdentity.from_seed(bytes([7]) * 32)
|
|
62
|
+
vendor = bytes([0x0A]) * 16
|
|
63
|
+
device_class = bytes([0x0B]) * 16
|
|
64
|
+
|
|
65
|
+
# The release. A manifest says who the image is for, which slot it belongs in, how big it
|
|
66
|
+
# is and what it hashes to; nothing about the image itself is taken on trust.
|
|
67
|
+
image = b"firmware for a flow meter, version two"
|
|
68
|
+
manifest = Manifest(
|
|
69
|
+
sequence=2,
|
|
70
|
+
vendor_id=vendor,
|
|
71
|
+
class_id=device_class,
|
|
72
|
+
storage=1,
|
|
73
|
+
digest=image_digest(image),
|
|
74
|
+
size=len(image),
|
|
75
|
+
)
|
|
76
|
+
envelope = sign_manifest(manifest, publisher)
|
|
77
|
+
print(f"published sequence {manifest.sequence} in a {len(envelope)}-byte envelope")
|
|
78
|
+
|
|
79
|
+
# On the device. It checks the envelope against the key it was anchored to before it
|
|
80
|
+
# accepts a single byte of the image.
|
|
81
|
+
opened = verify_envelope(envelope, publisher.public_key)
|
|
82
|
+
print(f"accepted a release for slot {opened.storage}")
|
|
83
|
+
|
|
84
|
+
# It left the factory running sequence 1 from slot 0, so the release goes to the spare slot
|
|
85
|
+
# and the image it is running stays where it is.
|
|
86
|
+
fleet = Updater(vendor, device_class, publisher.public_key, 2, 4096)
|
|
87
|
+
fleet.provision(0, 1)
|
|
88
|
+
fleet.begin(envelope)
|
|
89
|
+
for at in range(0, len(image), 16):
|
|
90
|
+
fleet.write(image[at : at + 16])
|
|
91
|
+
print(f"staged {fleet.progress().written} of {len(image)} bytes")
|
|
92
|
+
slot = fleet.finish()
|
|
93
|
+
print(f"written to slot {slot}, leaving the running image alone")
|
|
94
|
+
|
|
95
|
+
# The first boot into a new image is a trial. It reverts on the next boot unless the device
|
|
96
|
+
# confirms that it came up, which is what makes a bad release survivable.
|
|
97
|
+
print(f"booting {fleet.on_boot().action}")
|
|
98
|
+
fleet.confirm()
|
|
99
|
+
print(f"confirmed slot {slot} is now {fleet.slot_record(slot).state}")
|
|
100
|
+
|
|
101
|
+
# The same release signed by a key this device is not anchored to gets nowhere.
|
|
102
|
+
impostor = DeviceIdentity.from_seed(bytes([90]) * 32)
|
|
103
|
+
try:
|
|
104
|
+
fleet.stage(sign_manifest(manifest, impostor), image)
|
|
105
|
+
print("a forged release was accepted, which should never happen")
|
|
106
|
+
except PamojaError as error:
|
|
107
|
+
print(f"forged refused: {error}")
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## The same capability in every language
|
|
111
|
+
|
|
112
|
+
| Language | Package | Reference |
|
|
113
|
+
| --- | --- | --- |
|
|
114
|
+
| Rust | [`pamoja-update`](https://crates.io/crates/pamoja-update) | [reference](https://pamoja.molex.cloud/docs/reference/rust/pamoja_update/index.html), [docs.rs](https://docs.rs/pamoja-update), [install](https://pamoja.molex.cloud/docs/reference/rust.html#rust-update) |
|
|
115
|
+
| TypeScript | [`@pamoja/update`](https://www.npmjs.com/package/@pamoja/update) | [reference](https://pamoja.molex.cloud/docs/reference/node/modules/_pamoja_update.html), [install](https://pamoja.molex.cloud/docs/reference/node.html#node-update) |
|
|
116
|
+
| Python | [`pamoja-update`](https://pypi.org/project/pamoja-update/) | [reference](https://pamoja.molex.cloud/docs/reference/python/pamoja/update.html), [install](https://pamoja.molex.cloud/docs/reference/python.html#python-update) |
|
|
117
|
+
| C# | [`Pamoja.Update`](https://www.nuget.org/packages/Pamoja.Update) | [reference](https://pamoja.molex.cloud/docs/reference/dotnet/api/Pamoja.Update.html), [install](https://pamoja.molex.cloud/docs/reference/dotnet.html#dotnet-update) |
|
|
118
|
+
|
|
119
|
+
## Documentation
|
|
120
|
+
|
|
121
|
+
- [`pamoja.update` reference](https://pamoja.molex.cloud/docs/reference/python/pamoja/update.html), every class and function in this module.
|
|
122
|
+
- [The Signed updates guide](https://pamoja.molex.cloud/docs/guides/update.html), with the same example in Rust, TypeScript, and C#.
|
|
123
|
+
- [Every capability](https://pamoja.molex.cloud/docs/), and the [install page](https://pamoja.molex.cloud/docs/install.html).
|
|
124
|
+
|
|
125
|
+
## License
|
|
126
|
+
|
|
127
|
+
MIT
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# pamoja-update
|
|
2
|
+
|
|
3
|
+
Signed firmware manifests, streaming image verification, and A/B slots that fall back on their own. One capability of [pamoja](https://github.com/molexxxx/pamoja), one memory-safe Rust core with bindings for TypeScript, Python, and C#.
|
|
4
|
+
|
|
5
|
+
[](https://pamoja.molex.cloud/docs/guides/update.html)
|
|
6
|
+
[](https://pamoja.molex.cloud/docs/)
|
|
7
|
+
[](https://pamoja.molex.cloud/docs/reference/python/pamoja/update.html)
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```sh
|
|
12
|
+
pip install pamoja-update
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
```python
|
|
16
|
+
from pamoja import update
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
This pulls in `pamoja-native`, the compiled engine, and `pamoja-security`. `pip install pamoja` is the whole framework in one package.
|
|
20
|
+
|
|
21
|
+
## Example
|
|
22
|
+
|
|
23
|
+
The script the test suite runs, spliced here as it ran.
|
|
24
|
+
|
|
25
|
+
From [`bindings/python/guides/update.py`](https://github.com/molexxxx/pamoja/blob/main/bindings/python/guides/update.py):
|
|
26
|
+
|
|
27
|
+
```python
|
|
28
|
+
from pamoja.core import PamojaError
|
|
29
|
+
from pamoja.security import DeviceIdentity
|
|
30
|
+
from pamoja.update import (
|
|
31
|
+
BootAction,
|
|
32
|
+
Manifest,
|
|
33
|
+
SlotState,
|
|
34
|
+
Updater,
|
|
35
|
+
image_digest,
|
|
36
|
+
sign_manifest,
|
|
37
|
+
verify_envelope,
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
# The publisher's key signs releases; devices in the field are anchored to its public half
|
|
41
|
+
# and will take firmware from nobody else.
|
|
42
|
+
publisher = DeviceIdentity.from_seed(bytes([7]) * 32)
|
|
43
|
+
vendor = bytes([0x0A]) * 16
|
|
44
|
+
device_class = bytes([0x0B]) * 16
|
|
45
|
+
|
|
46
|
+
# The release. A manifest says who the image is for, which slot it belongs in, how big it
|
|
47
|
+
# is and what it hashes to; nothing about the image itself is taken on trust.
|
|
48
|
+
image = b"firmware for a flow meter, version two"
|
|
49
|
+
manifest = Manifest(
|
|
50
|
+
sequence=2,
|
|
51
|
+
vendor_id=vendor,
|
|
52
|
+
class_id=device_class,
|
|
53
|
+
storage=1,
|
|
54
|
+
digest=image_digest(image),
|
|
55
|
+
size=len(image),
|
|
56
|
+
)
|
|
57
|
+
envelope = sign_manifest(manifest, publisher)
|
|
58
|
+
print(f"published sequence {manifest.sequence} in a {len(envelope)}-byte envelope")
|
|
59
|
+
|
|
60
|
+
# On the device. It checks the envelope against the key it was anchored to before it
|
|
61
|
+
# accepts a single byte of the image.
|
|
62
|
+
opened = verify_envelope(envelope, publisher.public_key)
|
|
63
|
+
print(f"accepted a release for slot {opened.storage}")
|
|
64
|
+
|
|
65
|
+
# It left the factory running sequence 1 from slot 0, so the release goes to the spare slot
|
|
66
|
+
# and the image it is running stays where it is.
|
|
67
|
+
fleet = Updater(vendor, device_class, publisher.public_key, 2, 4096)
|
|
68
|
+
fleet.provision(0, 1)
|
|
69
|
+
fleet.begin(envelope)
|
|
70
|
+
for at in range(0, len(image), 16):
|
|
71
|
+
fleet.write(image[at : at + 16])
|
|
72
|
+
print(f"staged {fleet.progress().written} of {len(image)} bytes")
|
|
73
|
+
slot = fleet.finish()
|
|
74
|
+
print(f"written to slot {slot}, leaving the running image alone")
|
|
75
|
+
|
|
76
|
+
# The first boot into a new image is a trial. It reverts on the next boot unless the device
|
|
77
|
+
# confirms that it came up, which is what makes a bad release survivable.
|
|
78
|
+
print(f"booting {fleet.on_boot().action}")
|
|
79
|
+
fleet.confirm()
|
|
80
|
+
print(f"confirmed slot {slot} is now {fleet.slot_record(slot).state}")
|
|
81
|
+
|
|
82
|
+
# The same release signed by a key this device is not anchored to gets nowhere.
|
|
83
|
+
impostor = DeviceIdentity.from_seed(bytes([90]) * 32)
|
|
84
|
+
try:
|
|
85
|
+
fleet.stage(sign_manifest(manifest, impostor), image)
|
|
86
|
+
print("a forged release was accepted, which should never happen")
|
|
87
|
+
except PamojaError as error:
|
|
88
|
+
print(f"forged refused: {error}")
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## The same capability in every language
|
|
92
|
+
|
|
93
|
+
| Language | Package | Reference |
|
|
94
|
+
| --- | --- | --- |
|
|
95
|
+
| Rust | [`pamoja-update`](https://crates.io/crates/pamoja-update) | [reference](https://pamoja.molex.cloud/docs/reference/rust/pamoja_update/index.html), [docs.rs](https://docs.rs/pamoja-update), [install](https://pamoja.molex.cloud/docs/reference/rust.html#rust-update) |
|
|
96
|
+
| TypeScript | [`@pamoja/update`](https://www.npmjs.com/package/@pamoja/update) | [reference](https://pamoja.molex.cloud/docs/reference/node/modules/_pamoja_update.html), [install](https://pamoja.molex.cloud/docs/reference/node.html#node-update) |
|
|
97
|
+
| Python | [`pamoja-update`](https://pypi.org/project/pamoja-update/) | [reference](https://pamoja.molex.cloud/docs/reference/python/pamoja/update.html), [install](https://pamoja.molex.cloud/docs/reference/python.html#python-update) |
|
|
98
|
+
| C# | [`Pamoja.Update`](https://www.nuget.org/packages/Pamoja.Update) | [reference](https://pamoja.molex.cloud/docs/reference/dotnet/api/Pamoja.Update.html), [install](https://pamoja.molex.cloud/docs/reference/dotnet.html#dotnet-update) |
|
|
99
|
+
|
|
100
|
+
## Documentation
|
|
101
|
+
|
|
102
|
+
- [`pamoja.update` reference](https://pamoja.molex.cloud/docs/reference/python/pamoja/update.html), every class and function in this module.
|
|
103
|
+
- [The Signed updates guide](https://pamoja.molex.cloud/docs/guides/update.html), with the same example in Rust, TypeScript, and C#.
|
|
104
|
+
- [Every capability](https://pamoja.molex.cloud/docs/), and the [install page](https://pamoja.molex.cloud/docs/install.html).
|
|
105
|
+
|
|
106
|
+
## License
|
|
107
|
+
|
|
108
|
+
MIT
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
"""Idiomatic signed-update facade.
|
|
2
|
+
|
|
3
|
+
A device that cannot be fixed in the field is a device that has to be visited,
|
|
4
|
+
and some of them are a day's travel away. Signed updates make that a network
|
|
5
|
+
operation instead: a release carries a manifest naming who it is for and what it
|
|
6
|
+
hashes to, a device refuses anything not signed by the key it trusts, and an
|
|
7
|
+
image that fails to confirm itself is rolled back to the one that worked.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import enum
|
|
13
|
+
|
|
14
|
+
from pamoja._native import (
|
|
15
|
+
BootDecision,
|
|
16
|
+
Delegation,
|
|
17
|
+
ImageVerifier,
|
|
18
|
+
Manifest,
|
|
19
|
+
Progress,
|
|
20
|
+
SlotRecord,
|
|
21
|
+
Updater,
|
|
22
|
+
decode_manifest,
|
|
23
|
+
encode_manifest,
|
|
24
|
+
envelope_body,
|
|
25
|
+
open_delegation,
|
|
26
|
+
verify_envelope,
|
|
27
|
+
)
|
|
28
|
+
from pamoja._native import sign_delegation as _sign_delegation
|
|
29
|
+
from pamoja._native import image_digest as _image_digest
|
|
30
|
+
from pamoja._native import sign_manifest as _sign_manifest
|
|
31
|
+
from pamoja._native import update_format_raw as _format_raw
|
|
32
|
+
from pamoja._native import update_structure_version as _structure_version
|
|
33
|
+
from pamoja.security import DeviceIdentity
|
|
34
|
+
|
|
35
|
+
__all__ = [
|
|
36
|
+
"FORMAT_RAW",
|
|
37
|
+
"STRUCTURE_VERSION",
|
|
38
|
+
"BootAction",
|
|
39
|
+
"BootDecision",
|
|
40
|
+
"Delegation",
|
|
41
|
+
"ImageVerifier",
|
|
42
|
+
"Manifest",
|
|
43
|
+
"Progress",
|
|
44
|
+
"SlotRecord",
|
|
45
|
+
"SlotState",
|
|
46
|
+
"Updater",
|
|
47
|
+
"decode_manifest",
|
|
48
|
+
"encode_manifest",
|
|
49
|
+
"envelope_body",
|
|
50
|
+
"open_delegation",
|
|
51
|
+
"sign_delegation",
|
|
52
|
+
"image_digest",
|
|
53
|
+
"sign_manifest",
|
|
54
|
+
"verify_envelope",
|
|
55
|
+
]
|
|
56
|
+
|
|
57
|
+
#: The manifest structure version this build writes.
|
|
58
|
+
STRUCTURE_VERSION = _structure_version()
|
|
59
|
+
|
|
60
|
+
#: The payload format meaning the payload is the image itself, byte for byte.
|
|
61
|
+
FORMAT_RAW = _format_raw()
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class SlotState(str, enum.Enum):
|
|
65
|
+
"""What a device believes about one slot."""
|
|
66
|
+
|
|
67
|
+
#: Nothing has been written here.
|
|
68
|
+
EMPTY = "Empty"
|
|
69
|
+
#: An image is arriving, and ``written`` says how much of it has.
|
|
70
|
+
RECEIVING = "Receiving"
|
|
71
|
+
#: A complete image that matched its manifest, not yet tried.
|
|
72
|
+
STAGED = "Staged"
|
|
73
|
+
#: Being tried for the first time; it reverts unless it confirms.
|
|
74
|
+
PENDING = "Pending"
|
|
75
|
+
#: Tried and confirmed working.
|
|
76
|
+
CONFIRMED = "Confirmed"
|
|
77
|
+
#: Tried and did not confirm, so it will not be tried again.
|
|
78
|
+
FAILED = "Failed"
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class BootAction(str, enum.Enum):
|
|
82
|
+
"""What a bootloader should do with what it found."""
|
|
83
|
+
|
|
84
|
+
#: Nothing new to try; run the confirmed image.
|
|
85
|
+
CONFIRMED = "Confirmed"
|
|
86
|
+
#: A staged image is being tried for the first time.
|
|
87
|
+
TRYING = "Trying"
|
|
88
|
+
#: A pending image never confirmed, so it was failed.
|
|
89
|
+
REVERTED = "Reverted"
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def image_digest(image: bytes) -> bytes:
|
|
93
|
+
"""Hash a complete image, for a publisher filling in a manifest.
|
|
94
|
+
|
|
95
|
+
The manifest commits to a SHA-256 over the image, and this is that hash, so a
|
|
96
|
+
publisher does not need a hashing library of its own just to name the image it
|
|
97
|
+
is releasing.
|
|
98
|
+
|
|
99
|
+
:param image: The complete image the release carries.
|
|
100
|
+
:returns: The 32-byte digest to put in a :class:`Manifest`.
|
|
101
|
+
"""
|
|
102
|
+
return _image_digest(bytes(image))
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def sign_manifest(manifest: Manifest, author: DeviceIdentity) -> bytes:
|
|
106
|
+
"""Sign a manifest into the envelope that is offered to a device.
|
|
107
|
+
|
|
108
|
+
:param manifest: What the release says about itself.
|
|
109
|
+
:param author: The identity signing the release.
|
|
110
|
+
:returns: The signed envelope.
|
|
111
|
+
"""
|
|
112
|
+
return _sign_manifest(manifest, DeviceIdentity.native(author))
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def sign_delegation(delegation: Delegation, anchor: DeviceIdentity) -> bytes:
|
|
116
|
+
"""Sign a delegation, naming a release key the anchor stands behind.
|
|
117
|
+
|
|
118
|
+
Keeping the anchor offline and rotating a release key under it is the
|
|
119
|
+
arrangement to prefer, because the key that signs day to day is the one most
|
|
120
|
+
likely to be stolen.
|
|
121
|
+
|
|
122
|
+
:param delegation: The statement to sign.
|
|
123
|
+
:param anchor: The anchor identity, which is the root of the trust.
|
|
124
|
+
:returns: The signed delegation envelope.
|
|
125
|
+
"""
|
|
126
|
+
return _sign_delegation(delegation, DeviceIdentity.native(anchor))
|
|
File without changes
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling>=1.27"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "pamoja-update"
|
|
7
|
+
version = "0.1.18"
|
|
8
|
+
description = "Signed firmware manifests, streaming image verification, and A/B slots that fall back on their own."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = { text = "MIT" }
|
|
11
|
+
license-files = ["LICENSE-MIT"]
|
|
12
|
+
requires-python = ">=3.10"
|
|
13
|
+
authors = [{ name = "molexxxx" }]
|
|
14
|
+
keywords = ["pamoja", "iot", "robotics", "update"]
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Programming Language :: Python :: 3",
|
|
17
|
+
"License :: OSI Approved :: MIT License",
|
|
18
|
+
"Operating System :: OS Independent",
|
|
19
|
+
"Typing :: Typed",
|
|
20
|
+
]
|
|
21
|
+
dependencies = [
|
|
22
|
+
"pamoja-native==0.1.18",
|
|
23
|
+
"pamoja-security==0.1.18",
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
[project.urls]
|
|
27
|
+
Repository = "https://github.com/molexxxx/pamoja"
|
|
28
|
+
Documentation = "https://pamoja.molex.cloud/docs/guides/update.html"
|
|
29
|
+
|
|
30
|
+
[tool.hatch.build.targets.wheel]
|
|
31
|
+
packages = ["pamoja"]
|