@le-space/rootfs 0.4.0 → 0.4.2
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.
- package/package.json +1 -1
- package/reference/orbitdb-relay-pinner/rootfs/orbitdb-relay-pinner-bootstrap-refresh.py +13 -10
- package/reference/uc-go-peer/rootfs/uc-go-peer-bootstrap-deregister.py +6 -12
- package/reference/uc-go-peer/rootfs/uc-go-peer-bootstrap-refresh.py +11 -14
- package/reference/uc-go-peer/rootfs/uc-go-peer-setup-server.py +244 -2
package/package.json
CHANGED
|
@@ -306,19 +306,22 @@ def sign_aleph_message(unsigned_message: dict[str, object], private_key: str) ->
|
|
|
306
306
|
|
|
307
307
|
def broadcast_aleph_message(api_host: str, message: dict[str, object]) -> tuple[int, dict]:
|
|
308
308
|
url = urllib.parse.urljoin(api_host.rstrip("/") + "/", "api/v0/messages")
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
{**message, "sync": True},
|
|
312
|
-
dict(message),
|
|
313
|
-
]
|
|
309
|
+
request_body = {"sync": True, "message": message}
|
|
310
|
+
max_attempts = 3
|
|
314
311
|
last: tuple[int, dict] | None = None
|
|
315
|
-
for
|
|
316
|
-
last = post_json(url,
|
|
312
|
+
for index in range(max_attempts):
|
|
313
|
+
last = post_json(url, request_body)
|
|
317
314
|
if 200 <= last[0] < 300:
|
|
318
315
|
return last
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
316
|
+
publication_status = last[1].get("publication_status") if isinstance(last[1], dict) else None
|
|
317
|
+
retryable = last[0] >= 500 or (
|
|
318
|
+
isinstance(publication_status, dict)
|
|
319
|
+
and isinstance(publication_status.get("status"), str)
|
|
320
|
+
and publication_status.get("status").strip().lower() == "error"
|
|
321
|
+
)
|
|
322
|
+
if index >= max_attempts - 1 or not retryable:
|
|
323
|
+
raise RuntimeError(f"Aleph broadcast failed: {last[0]} {json_dumps(last[1])}")
|
|
324
|
+
raise RuntimeError("Aleph broadcast failed: retry budget exhausted")
|
|
322
325
|
|
|
323
326
|
|
|
324
327
|
def parse_post_record(entry: object) -> dict[str, object] | None:
|
|
@@ -155,22 +155,16 @@ def is_retryable_broadcast_failure(http_status: int, payload: object) -> bool:
|
|
|
155
155
|
|
|
156
156
|
def broadcast_aleph_message(api_host: str, message: dict[str, object]) -> tuple[int, object]:
|
|
157
157
|
url = urllib.parse.urljoin(api_host.rstrip("/") + "/", "api/v0/messages")
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
]
|
|
163
|
-
for index, attempt in enumerate(attempts):
|
|
164
|
-
http_status, payload = post_json(url, attempt)
|
|
158
|
+
request_body = {"sync": True, "message": message}
|
|
159
|
+
max_attempts = 3
|
|
160
|
+
for index in range(max_attempts):
|
|
161
|
+
http_status, payload = post_json(url, request_body)
|
|
165
162
|
if 200 <= http_status < 300:
|
|
166
163
|
return http_status, payload
|
|
167
|
-
can_retry = index <
|
|
168
|
-
is_invalid_message_format(http_status, payload)
|
|
169
|
-
or is_retryable_broadcast_failure(http_status, payload)
|
|
170
|
-
)
|
|
164
|
+
can_retry = index < max_attempts - 1 and is_retryable_broadcast_failure(http_status, payload)
|
|
171
165
|
if not can_retry:
|
|
172
166
|
raise RuntimeError(f"Aleph broadcast failed: {http_status} {json_dumps(payload)}")
|
|
173
|
-
raise RuntimeError("Aleph broadcast failed:
|
|
167
|
+
raise RuntimeError("Aleph broadcast failed: retry budget exhausted")
|
|
174
168
|
|
|
175
169
|
|
|
176
170
|
def parse_post_record(entry: object) -> dict[str, object] | None:
|
|
@@ -246,13 +246,13 @@ def build_post_content(
|
|
|
246
246
|
owner_authorization: dict[str, object] | None,
|
|
247
247
|
relay_proof: dict[str, object],
|
|
248
248
|
now_ms: int,
|
|
249
|
+
now_seconds: float,
|
|
249
250
|
ref: str,
|
|
250
251
|
post_type: str,
|
|
251
252
|
) -> dict[str, object]:
|
|
252
253
|
content = {
|
|
253
254
|
"peerId": peer_id,
|
|
254
255
|
"multiaddrs": multiaddrs,
|
|
255
|
-
"browserMultiaddrs": browser_multiaddrs if browser_multiaddrs else None,
|
|
256
256
|
"registrationId": registration_id,
|
|
257
257
|
"profile": profile,
|
|
258
258
|
"version": version,
|
|
@@ -266,12 +266,14 @@ def build_post_content(
|
|
|
266
266
|
"relayProof": relay_proof,
|
|
267
267
|
"updatedAt": now_ms,
|
|
268
268
|
}
|
|
269
|
+
if browser_multiaddrs:
|
|
270
|
+
content["browserMultiaddrs"] = browser_multiaddrs
|
|
269
271
|
return {
|
|
270
272
|
"type": post_type,
|
|
271
273
|
"address": sender,
|
|
272
274
|
"ref": ref,
|
|
273
275
|
"content": content,
|
|
274
|
-
"time":
|
|
276
|
+
"time": now_seconds,
|
|
275
277
|
}
|
|
276
278
|
|
|
277
279
|
|
|
@@ -347,22 +349,16 @@ def is_retryable_broadcast_failure(http_status: int, payload: object) -> bool:
|
|
|
347
349
|
|
|
348
350
|
def broadcast_aleph_message(api_host: str, message: dict[str, object]) -> tuple[int, object]:
|
|
349
351
|
url = urllib.parse.urljoin(api_host.rstrip("/") + "/", "api/v0/messages")
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
]
|
|
355
|
-
for index, attempt in enumerate(attempts):
|
|
356
|
-
http_status, payload = post_json(url, attempt)
|
|
352
|
+
request_body = {"sync": True, "message": message}
|
|
353
|
+
max_attempts = 3
|
|
354
|
+
for index in range(max_attempts):
|
|
355
|
+
http_status, payload = post_json(url, request_body)
|
|
357
356
|
if 200 <= http_status < 300:
|
|
358
357
|
return http_status, payload
|
|
359
|
-
can_retry = index <
|
|
360
|
-
is_invalid_message_format(http_status, payload)
|
|
361
|
-
or is_retryable_broadcast_failure(http_status, payload)
|
|
362
|
-
)
|
|
358
|
+
can_retry = index < max_attempts - 1 and is_retryable_broadcast_failure(http_status, payload)
|
|
363
359
|
if not can_retry:
|
|
364
360
|
raise RuntimeError(f"Aleph broadcast failed: {http_status} {json_dumps(payload)}")
|
|
365
|
-
raise RuntimeError("Aleph broadcast failed:
|
|
361
|
+
raise RuntimeError("Aleph broadcast failed: retry budget exhausted")
|
|
366
362
|
|
|
367
363
|
|
|
368
364
|
def parse_post_record(entry: object) -> dict[str, object] | None:
|
|
@@ -538,6 +534,7 @@ def main() -> None:
|
|
|
538
534
|
owner_authorization,
|
|
539
535
|
relay_proof,
|
|
540
536
|
now_ms,
|
|
537
|
+
now_seconds,
|
|
541
538
|
ref,
|
|
542
539
|
post_type,
|
|
543
540
|
)
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
|
+
import base64
|
|
3
|
+
import hashlib
|
|
2
4
|
import ipaddress
|
|
3
5
|
import json
|
|
4
6
|
import os
|
|
@@ -7,6 +9,8 @@ import threading
|
|
|
7
9
|
import time
|
|
8
10
|
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
|
9
11
|
from pathlib import Path
|
|
12
|
+
from urllib import error as urllib_error
|
|
13
|
+
from urllib import request as urllib_request
|
|
10
14
|
from urllib.error import HTTPError, URLError
|
|
11
15
|
from urllib.parse import urlencode, urlsplit
|
|
12
16
|
from urllib.request import urlopen
|
|
@@ -23,12 +27,36 @@ AUTHORIZED_KEYS_FILE = os.environ.get("AUTHORIZED_KEYS_FILE", "/root/.ssh/author
|
|
|
23
27
|
ALEPH_API_HOST = os.environ.get("ALEPH_API_HOST", "https://api2.aleph.im")
|
|
24
28
|
BOOTSTRAP_CONFIG_KEY = os.environ.get("BOOTSTRAP_CONFIG_KEY", "vm-bootstrap-config")
|
|
25
29
|
BOOTSTRAP_CONFIG_POLL_SECONDS = float(os.environ.get("BOOTSTRAP_CONFIG_POLL_SECONDS", "5"))
|
|
30
|
+
BOOTSTRAP_CONFIG_SIGNAL_REF = os.environ.get("BOOTSTRAP_CONFIG_SIGNAL_REF", "vm-bootstrap-config")
|
|
31
|
+
BOOTSTRAP_CONFIG_SIGNAL_POST_TYPE = os.environ.get(
|
|
32
|
+
"BOOTSTRAP_CONFIG_SIGNAL_POST_TYPE", "vm-bootstrap-config-status"
|
|
33
|
+
)
|
|
34
|
+
BOOTSTRAP_CHANNEL = os.environ.get("ALEPH_BOOTSTRAP_CHANNEL", "simple-todo")
|
|
26
35
|
DESCRIBE_TIMEOUT_SECONDS = int(os.environ.get("DESCRIBE_TIMEOUT_SECONDS", "15"))
|
|
27
36
|
DESCRIBE_INTERVAL_SECONDS = float(os.environ.get("DESCRIBE_INTERVAL_SECONDS", "1"))
|
|
28
37
|
|
|
29
38
|
CONFIGURE_LOCK = threading.Lock()
|
|
30
39
|
|
|
31
40
|
|
|
41
|
+
def _json_dumps(payload: object) -> str:
|
|
42
|
+
return json.dumps(payload, separators=(",", ":"))
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _parse_env_file(path: str) -> dict[str, str]:
|
|
46
|
+
values: dict[str, str] = {}
|
|
47
|
+
if not os.path.exists(path):
|
|
48
|
+
return values
|
|
49
|
+
|
|
50
|
+
with open(path, encoding="utf-8") as handle:
|
|
51
|
+
for line in handle:
|
|
52
|
+
stripped = line.strip()
|
|
53
|
+
if not stripped or stripped.startswith("#") or "=" not in stripped:
|
|
54
|
+
continue
|
|
55
|
+
key, value = stripped.split("=", 1)
|
|
56
|
+
values[key.strip()] = value.strip()
|
|
57
|
+
return values
|
|
58
|
+
|
|
59
|
+
|
|
32
60
|
def _cors_headers(handler: BaseHTTPRequestHandler) -> None:
|
|
33
61
|
handler.send_header("Access-Control-Allow-Origin", "*")
|
|
34
62
|
handler.send_header("Access-Control-Allow-Methods", "GET, POST, OPTIONS")
|
|
@@ -143,7 +171,7 @@ def _build_configure_args(record: dict) -> list[str]:
|
|
|
143
171
|
return args
|
|
144
172
|
|
|
145
173
|
|
|
146
|
-
def _run_configure_process(args: list[str]) -> tuple[bool, str]:
|
|
174
|
+
def _run_configure_process(args: list[str], bootstrap_record: dict | None = None) -> tuple[bool, str]:
|
|
147
175
|
with CONFIGURE_LOCK:
|
|
148
176
|
if os.path.exists(READY_FILE):
|
|
149
177
|
return True, "ready"
|
|
@@ -153,9 +181,223 @@ def _run_configure_process(args: list[str]) -> tuple[bool, str]:
|
|
|
153
181
|
return False, error.stderr.strip() or error.stdout.strip() or str(error)
|
|
154
182
|
|
|
155
183
|
_start_metadata_generation()
|
|
184
|
+
if isinstance(bootstrap_record, dict):
|
|
185
|
+
try:
|
|
186
|
+
_publish_vm_bootstrap_config_signal(bootstrap_record)
|
|
187
|
+
except Exception as error: # pragma: no cover - runtime error path
|
|
188
|
+
return False, f"configured relay but failed to publish bootstrap config signal: {error}"
|
|
156
189
|
return True, result.stdout.strip()
|
|
157
190
|
|
|
158
191
|
|
|
192
|
+
def _address_from_private_key(private_key: str) -> str:
|
|
193
|
+
try:
|
|
194
|
+
from eth_account import Account
|
|
195
|
+
except ImportError as error: # pragma: no cover - runtime dependency
|
|
196
|
+
raise RuntimeError(
|
|
197
|
+
"eth-account is required for guest-side bootstrap status publishing"
|
|
198
|
+
) from error
|
|
199
|
+
|
|
200
|
+
return Account.from_key(private_key).address
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def _sign_personal_message(private_key: str, payload: str) -> str:
|
|
204
|
+
try:
|
|
205
|
+
from eth_account import Account
|
|
206
|
+
from eth_account.messages import encode_defunct
|
|
207
|
+
except ImportError as error: # pragma: no cover - runtime dependency
|
|
208
|
+
raise RuntimeError(
|
|
209
|
+
"eth-account is required for guest-side bootstrap status publishing"
|
|
210
|
+
) from error
|
|
211
|
+
|
|
212
|
+
message = encode_defunct(text=payload)
|
|
213
|
+
signed = Account.sign_message(message, private_key=private_key)
|
|
214
|
+
signature = signed.signature.hex()
|
|
215
|
+
return signature if signature.startswith("0x") else f"0x{signature}"
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def _signature_payload(chain: str, sender: str, message_type: str, item_hash: str) -> str:
|
|
219
|
+
return "\n".join([chain, sender, message_type, item_hash])
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def _iter_validation_errors(payload: object) -> list[dict]:
|
|
223
|
+
if isinstance(payload, list):
|
|
224
|
+
return [entry for entry in payload if isinstance(entry, dict)]
|
|
225
|
+
return []
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
def _is_invalid_message_format(http_status: int, payload: object) -> bool:
|
|
229
|
+
if http_status != 422:
|
|
230
|
+
return False
|
|
231
|
+
|
|
232
|
+
if isinstance(payload, dict):
|
|
233
|
+
details = payload.get("details")
|
|
234
|
+
if isinstance(details, str) and "InvalidMessageFormat" in details:
|
|
235
|
+
return True
|
|
236
|
+
if isinstance(details, dict):
|
|
237
|
+
message = details.get("message")
|
|
238
|
+
if isinstance(message, str) and "InvalidMessageFormat" in message:
|
|
239
|
+
return True
|
|
240
|
+
|
|
241
|
+
for entry in _iter_validation_errors(payload):
|
|
242
|
+
message = entry.get("msg")
|
|
243
|
+
location = entry.get("loc")
|
|
244
|
+
if isinstance(message, str) and "InvalidMessageFormat" in message:
|
|
245
|
+
return True
|
|
246
|
+
if message == "Field required" and location == ["message"]:
|
|
247
|
+
return True
|
|
248
|
+
|
|
249
|
+
return False
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
def _is_retryable_broadcast_failure(http_status: int, payload: object) -> bool:
|
|
253
|
+
if http_status >= 500:
|
|
254
|
+
return True
|
|
255
|
+
if isinstance(payload, dict):
|
|
256
|
+
publication_status = payload.get("publication_status")
|
|
257
|
+
if isinstance(publication_status, dict):
|
|
258
|
+
status = publication_status.get("status")
|
|
259
|
+
if isinstance(status, str) and status.strip().lower() == "error":
|
|
260
|
+
return True
|
|
261
|
+
return False
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def _post_json(url: str, body: dict[str, object]) -> tuple[int, object]:
|
|
265
|
+
data = _json_dumps(body).encode("utf-8")
|
|
266
|
+
request = urllib_request.Request(
|
|
267
|
+
url,
|
|
268
|
+
data=data,
|
|
269
|
+
headers={"content-type": "application/json"},
|
|
270
|
+
method="POST",
|
|
271
|
+
)
|
|
272
|
+
try:
|
|
273
|
+
with urllib_request.urlopen(request, timeout=30) as response:
|
|
274
|
+
payload = response.read().decode("utf-8")
|
|
275
|
+
return response.status, json.loads(payload or "{}")
|
|
276
|
+
except urllib_error.HTTPError as error:
|
|
277
|
+
payload = error.read().decode("utf-8")
|
|
278
|
+
try:
|
|
279
|
+
return error.code, json.loads(payload or "{}")
|
|
280
|
+
except json.JSONDecodeError:
|
|
281
|
+
return error.code, {"details": payload}
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
def _broadcast_aleph_message(api_host: str, message: dict[str, object]) -> tuple[int, object]:
|
|
285
|
+
url = f"{api_host.rstrip('/')}/api/v0/messages"
|
|
286
|
+
request_body = {"sync": True, "message": message}
|
|
287
|
+
max_attempts = 3
|
|
288
|
+
for index in range(max_attempts):
|
|
289
|
+
http_status, payload = _post_json(url, request_body)
|
|
290
|
+
if 200 <= http_status < 300:
|
|
291
|
+
return http_status, payload
|
|
292
|
+
can_retry = index < max_attempts - 1 and _is_retryable_broadcast_failure(http_status, payload)
|
|
293
|
+
if not can_retry:
|
|
294
|
+
raise RuntimeError(
|
|
295
|
+
f"Aleph bootstrap status broadcast failed: {http_status} {_json_dumps(payload)}"
|
|
296
|
+
)
|
|
297
|
+
raise RuntimeError("Aleph bootstrap status broadcast failed: retry budget exhausted")
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
def _publish_vm_bootstrap_config_signal(record: dict) -> None:
|
|
301
|
+
env_values = _parse_env_file(ENV_FILE)
|
|
302
|
+
publisher_private_key = env_values.get("ALEPH_BOOTSTRAP_PUBLISHER_PRIVATE_KEY", "").strip()
|
|
303
|
+
if not publisher_private_key:
|
|
304
|
+
raise RuntimeError("missing ALEPH_BOOTSTRAP_PUBLISHER_PRIVATE_KEY")
|
|
305
|
+
|
|
306
|
+
owner_authorization_b64 = env_values.get("ALEPH_BOOTSTRAP_OWNER_AUTHORIZATION_B64", "").strip()
|
|
307
|
+
owner_authorization = None
|
|
308
|
+
if owner_authorization_b64:
|
|
309
|
+
decoded = base64.b64decode(owner_authorization_b64).decode("utf-8")
|
|
310
|
+
parsed = json.loads(decoded)
|
|
311
|
+
if isinstance(parsed, dict):
|
|
312
|
+
owner_authorization = parsed
|
|
313
|
+
|
|
314
|
+
owner_address = ""
|
|
315
|
+
if isinstance(owner_authorization, dict):
|
|
316
|
+
payload = owner_authorization.get("payload")
|
|
317
|
+
if isinstance(payload, dict):
|
|
318
|
+
owner_address = str(payload.get("ownerAddress") or "").strip()
|
|
319
|
+
if not owner_address:
|
|
320
|
+
owner_address = str(record.get("ownerAddress") or "").strip()
|
|
321
|
+
if not owner_address:
|
|
322
|
+
raise RuntimeError("missing ownerAddress for bootstrap config signal")
|
|
323
|
+
|
|
324
|
+
deployment_token = str(record.get("deploymentToken") or "").strip()
|
|
325
|
+
profile = str(record.get("profile") or "").strip()
|
|
326
|
+
instance_item_hash = str(record.get("instanceItemHash") or "").strip()
|
|
327
|
+
if not deployment_token or not profile or not instance_item_hash:
|
|
328
|
+
raise RuntimeError("bootstrap config signal is missing deployment metadata")
|
|
329
|
+
|
|
330
|
+
metadata, metadata_error = _fresh_metadata_payload()
|
|
331
|
+
if not metadata:
|
|
332
|
+
raise RuntimeError(
|
|
333
|
+
f"bootstrap config signal metadata is not ready: {metadata_error or 'metadata unavailable'}"
|
|
334
|
+
)
|
|
335
|
+
|
|
336
|
+
peer_id = metadata.get("peer_id")
|
|
337
|
+
probe_multiaddrs = metadata.get("probe_multiaddrs")
|
|
338
|
+
browser_bootstrap_multiaddrs = metadata.get("browser_bootstrap_multiaddrs")
|
|
339
|
+
if not isinstance(peer_id, str) or not peer_id.strip():
|
|
340
|
+
raise RuntimeError("bootstrap config signal is missing peer_id")
|
|
341
|
+
if not isinstance(probe_multiaddrs, list) or not any(
|
|
342
|
+
isinstance(entry, str) and entry.strip() for entry in probe_multiaddrs
|
|
343
|
+
):
|
|
344
|
+
raise RuntimeError("bootstrap config signal is missing probe_multiaddrs")
|
|
345
|
+
if not isinstance(browser_bootstrap_multiaddrs, list):
|
|
346
|
+
browser_bootstrap_multiaddrs = []
|
|
347
|
+
|
|
348
|
+
publisher_address = _address_from_private_key(publisher_private_key)
|
|
349
|
+
now_ms = int(time.time() * 1000)
|
|
350
|
+
content = {
|
|
351
|
+
"type": BOOTSTRAP_CONFIG_SIGNAL_POST_TYPE,
|
|
352
|
+
"address": publisher_address,
|
|
353
|
+
"ref": BOOTSTRAP_CONFIG_SIGNAL_REF,
|
|
354
|
+
"content": {
|
|
355
|
+
"deploymentToken": deployment_token,
|
|
356
|
+
"status": "applied",
|
|
357
|
+
"profile": profile,
|
|
358
|
+
"ownerAddress": owner_address,
|
|
359
|
+
"instanceItemHash": instance_item_hash,
|
|
360
|
+
"updatedAt": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime(now_ms / 1000)),
|
|
361
|
+
"publisherAddress": publisher_address,
|
|
362
|
+
"authorization": owner_authorization,
|
|
363
|
+
"peerId": peer_id.strip(),
|
|
364
|
+
"probeMultiaddrs": [
|
|
365
|
+
entry.strip()
|
|
366
|
+
for entry in probe_multiaddrs
|
|
367
|
+
if isinstance(entry, str) and entry.strip()
|
|
368
|
+
],
|
|
369
|
+
"browserBootstrapMultiaddrs": [
|
|
370
|
+
entry.strip()
|
|
371
|
+
for entry in browser_bootstrap_multiaddrs
|
|
372
|
+
if isinstance(entry, str) and entry.strip()
|
|
373
|
+
],
|
|
374
|
+
},
|
|
375
|
+
"time": now_ms,
|
|
376
|
+
}
|
|
377
|
+
item_content = _json_dumps(content)
|
|
378
|
+
unsigned_message = {
|
|
379
|
+
"channel": BOOTSTRAP_CHANNEL,
|
|
380
|
+
"sender": publisher_address,
|
|
381
|
+
"chain": "ETH",
|
|
382
|
+
"type": "POST",
|
|
383
|
+
"time": now_ms / 1000,
|
|
384
|
+
"item_type": "inline",
|
|
385
|
+
"item_content": item_content,
|
|
386
|
+
"item_hash": hashlib.sha256(item_content.encode("utf-8")).hexdigest(),
|
|
387
|
+
}
|
|
388
|
+
signed_message = dict(unsigned_message)
|
|
389
|
+
signed_message["signature"] = _sign_personal_message(
|
|
390
|
+
publisher_private_key,
|
|
391
|
+
_signature_payload(
|
|
392
|
+
str(unsigned_message["chain"]),
|
|
393
|
+
str(unsigned_message["sender"]),
|
|
394
|
+
str(unsigned_message["type"]),
|
|
395
|
+
str(unsigned_message["item_hash"]),
|
|
396
|
+
),
|
|
397
|
+
)
|
|
398
|
+
_broadcast_aleph_message(ALEPH_API_HOST, signed_message)
|
|
399
|
+
|
|
400
|
+
|
|
159
401
|
def _load_vm_bootstrap_record() -> tuple[str | None, str | None, dict | None]:
|
|
160
402
|
owner_address, deployment_token = _extract_guest_bootstrap_locator()
|
|
161
403
|
if not owner_address or not deployment_token:
|
|
@@ -197,7 +439,7 @@ def _poll_bootstrap_config_loop() -> None:
|
|
|
197
439
|
except ValueError:
|
|
198
440
|
time.sleep(BOOTSTRAP_CONFIG_POLL_SECONDS)
|
|
199
441
|
continue
|
|
200
|
-
ok, _ = _run_configure_process(args)
|
|
442
|
+
ok, _ = _run_configure_process(args, record)
|
|
201
443
|
if ok:
|
|
202
444
|
return
|
|
203
445
|
time.sleep(BOOTSTRAP_CONFIG_POLL_SECONDS)
|