@le-space/rootfs 0.6.24 → 0.6.26

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@le-space/rootfs",
3
- "version": "0.6.24",
3
+ "version": "0.6.26",
4
4
  "description": "Shared rootfs contract parsing, reference profile assets, and build helpers.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -27,9 +27,7 @@ DESCRIBE_SCRIPT = os.environ.get(
27
27
  DEFAULT_API_HOST = os.environ.get("ALEPH_BOOTSTRAP_API_HOST", "https://api.aleph.im")
28
28
  DEFAULT_CHANNEL = os.environ.get("ALEPH_BOOTSTRAP_CHANNEL", "simple-todo")
29
29
  DEFAULT_REF = os.environ.get("ALEPH_BOOTSTRAP_REF", "simple-todo-bootstrap")
30
- COMPACT_POST_TYPE = "relay-bootstrap-v2"
31
- LEGACY_POST_TYPE = "relay-bootstrap"
32
- DEFAULT_POST_TYPE = os.environ.get("ALEPH_BOOTSTRAP_POST_TYPE", COMPACT_POST_TYPE)
30
+ POST_TYPE = "relay-bootstrap-v2"
33
31
  DEFAULT_PROFILE = os.environ.get("ALEPH_BOOTSTRAP_PROFILE", "orbitdb-relay")
34
32
  MAX_PREVIOUS_PAGES = int(os.environ.get("ALEPH_BOOTSTRAP_MAX_PREVIOUS_PAGES", "5"))
35
33
  PAGINATION = int(os.environ.get("ALEPH_BOOTSTRAP_PAGINATION", "50"))
@@ -430,6 +428,15 @@ def parse_post_record(entry: object) -> dict[str, object] | None:
430
428
  item_content = None
431
429
 
432
430
  content_source = item_content if isinstance(item_content, dict) else entry
431
+ record_type = content_source.get("type")
432
+ if record_type == "relay-bootstrap":
433
+ raise RuntimeError(
434
+ "Legacy relay-bootstrap record encountered. Only relay-bootstrap-v2 is supported."
435
+ )
436
+ if isinstance(record_type, str) and record_type and record_type != POST_TYPE:
437
+ raise RuntimeError(
438
+ f"Unsupported relay bootstrap post type: {record_type}. Expected {POST_TYPE}."
439
+ )
433
440
  content = content_source.get("content")
434
441
  if not isinstance(content, dict):
435
442
  return None
@@ -465,7 +472,7 @@ def fetch_previous_hashes(
465
472
  api_host: str,
466
473
  channel: str,
467
474
  ref: str,
468
- post_types: list[str],
475
+ post_type: str,
469
476
  sender: str,
470
477
  current_item_hash: str,
471
478
  now_ms: int,
@@ -473,38 +480,37 @@ def fetch_previous_hashes(
473
480
  stale_cutoff_ms = now_ms - (STALE_RECORD_MAX_AGE_SECONDS * 1000)
474
481
  normalized_sender = normalize_address(sender)
475
482
  found: list[str] = []
476
- for post_type in post_types:
477
- for page in range(1, MAX_PREVIOUS_PAGES + 1):
478
- url = (
479
- f"{api_host.rstrip('/')}/api/v0/posts.json?"
480
- f"channels={urllib.parse.quote(channel)}&"
481
- f"refs={urllib.parse.quote(ref)}&"
482
- f"types={urllib.parse.quote(post_type)}&"
483
- f"pagination={PAGINATION}&page={page}"
484
- )
485
- payload = get_json(url)
486
- posts = payload.get("posts")
487
- if not isinstance(posts, list):
488
- break
489
-
490
- for entry in posts:
491
- parsed = parse_post_record(entry)
492
- if parsed is None:
493
- continue
494
- same_publisher = normalized_sender in {
495
- normalize_address(parsed["sender"]),
496
- normalize_address(parsed["publisher_address"]),
497
- }
498
- if not same_publisher:
499
- continue
500
- if parsed["item_hash"] == current_item_hash:
501
- continue
502
- updated_at = timestamp_ms(parsed["updated_at"])
503
- if updated_at is not None and updated_at < stale_cutoff_ms:
504
- found.append(str(parsed["item_hash"]))
505
-
506
- if len(posts) < PAGINATION:
507
- break
483
+ for page in range(1, MAX_PREVIOUS_PAGES + 1):
484
+ url = (
485
+ f"{api_host.rstrip('/')}/api/v0/posts.json?"
486
+ f"channels={urllib.parse.quote(channel)}&"
487
+ f"refs={urllib.parse.quote(ref)}&"
488
+ f"types={urllib.parse.quote(post_type)}&"
489
+ f"pagination={PAGINATION}&page={page}"
490
+ )
491
+ payload = get_json(url)
492
+ posts = payload.get("posts")
493
+ if not isinstance(posts, list):
494
+ break
495
+
496
+ for entry in posts:
497
+ parsed = parse_post_record(entry)
498
+ if parsed is None:
499
+ continue
500
+ same_publisher = normalized_sender in {
501
+ normalize_address(parsed["sender"]),
502
+ normalize_address(parsed["publisher_address"]),
503
+ }
504
+ if not same_publisher:
505
+ continue
506
+ if parsed["item_hash"] == current_item_hash:
507
+ continue
508
+ updated_at = timestamp_ms(parsed["updated_at"])
509
+ if updated_at is not None and updated_at < stale_cutoff_ms:
510
+ found.append(str(parsed["item_hash"]))
511
+
512
+ if len(posts) < PAGINATION:
513
+ break
508
514
 
509
515
  return dedupe(found)
510
516
 
@@ -568,18 +574,16 @@ def main() -> None:
568
574
  version = env_values.get("ALEPH_BOOTSTRAP_VERSION", "").strip() or None
569
575
  channel = env_values.get("ALEPH_BOOTSTRAP_CHANNEL", DEFAULT_CHANNEL).strip() or DEFAULT_CHANNEL
570
576
  ref = env_values.get("ALEPH_BOOTSTRAP_REF", DEFAULT_REF).strip() or DEFAULT_REF
571
- post_type = env_values.get("ALEPH_BOOTSTRAP_POST_TYPE", DEFAULT_POST_TYPE).strip() or DEFAULT_POST_TYPE
577
+ post_type = env_values.get("ALEPH_BOOTSTRAP_POST_TYPE", POST_TYPE).strip() or POST_TYPE
578
+ if post_type != POST_TYPE:
579
+ raise RuntimeError(
580
+ f"Unsupported ALEPH_BOOTSTRAP_POST_TYPE: {post_type}. Expected {POST_TYPE}."
581
+ )
572
582
  api_host = env_values.get("ALEPH_BOOTSTRAP_API_HOST", DEFAULT_API_HOST).strip() or DEFAULT_API_HOST
573
- compact = post_type == COMPACT_POST_TYPE
574
- published_multiaddrs = (
575
- select_compact_multiaddrs(browser_multiaddrs or multiaddrs)
576
- if compact
577
- else multiaddrs
578
- )
579
- published_browser_multiaddrs = [] if compact else browser_multiaddrs
583
+ published_multiaddrs = select_compact_multiaddrs(browser_multiaddrs or multiaddrs)
584
+ published_browser_multiaddrs: list[str] = []
580
585
  if not published_multiaddrs:
581
- reason = "no public browser multiaddrs" if compact else "no public multiaddrs"
582
- print(json_dumps({"status": "skipped", "reason": reason}))
586
+ print(json_dumps({"status": "skipped", "reason": "no public browser multiaddrs"}))
583
587
  return
584
588
 
585
589
  owner_authorization = load_owner_authorization(
@@ -634,7 +638,7 @@ def main() -> None:
634
638
  api_host,
635
639
  channel,
636
640
  ref,
637
- [post_type, LEGACY_POST_TYPE] if compact else [post_type],
641
+ post_type,
638
642
  publisher_address,
639
643
  str(unsigned_message["item_hash"]),
640
644
  now_ms,
@@ -23,7 +23,7 @@ DESCRIBE_SCRIPT = os.environ.get("DESCRIBE_SCRIPT", "/usr/local/sbin/uc-go-peer-
23
23
  DEFAULT_API_HOST = os.environ.get("ALEPH_BOOTSTRAP_API_HOST", "https://api.aleph.im")
24
24
  DEFAULT_CHANNEL = os.environ.get("ALEPH_BOOTSTRAP_CHANNEL", "simple-todo")
25
25
  DEFAULT_REF = os.environ.get("ALEPH_BOOTSTRAP_REF", "simple-todo-bootstrap")
26
- DEFAULT_POST_TYPE = os.environ.get("ALEPH_BOOTSTRAP_POST_TYPE", "relay-bootstrap")
26
+ POST_TYPE = "relay-bootstrap-v2"
27
27
  MAX_PREVIOUS_PAGES = int(os.environ.get("ALEPH_BOOTSTRAP_MAX_PREVIOUS_PAGES", "5"))
28
28
  PAGINATION = int(os.environ.get("ALEPH_BOOTSTRAP_PAGINATION", "50"))
29
29
 
@@ -185,6 +185,16 @@ def parse_post_record(entry: object) -> dict[str, object] | None:
185
185
  if not isinstance(item_content, dict):
186
186
  return None
187
187
 
188
+ record_type = item_content.get("type")
189
+ if record_type == "relay-bootstrap":
190
+ raise RuntimeError(
191
+ "Legacy relay-bootstrap record encountered. Only relay-bootstrap-v2 is supported."
192
+ )
193
+ if isinstance(record_type, str) and record_type and record_type != POST_TYPE:
194
+ raise RuntimeError(
195
+ f"Unsupported relay bootstrap post type: {record_type}. Expected {POST_TYPE}."
196
+ )
197
+
188
198
  content = item_content.get("content")
189
199
  if not isinstance(content, dict):
190
200
  return None
@@ -297,7 +307,11 @@ def main() -> None:
297
307
  peer_id = load_peer_id()
298
308
  channel = env_values.get("ALEPH_BOOTSTRAP_CHANNEL", DEFAULT_CHANNEL).strip() or DEFAULT_CHANNEL
299
309
  ref = env_values.get("ALEPH_BOOTSTRAP_REF", DEFAULT_REF).strip() or DEFAULT_REF
300
- post_type = env_values.get("ALEPH_BOOTSTRAP_POST_TYPE", DEFAULT_POST_TYPE).strip() or DEFAULT_POST_TYPE
310
+ post_type = env_values.get("ALEPH_BOOTSTRAP_POST_TYPE", POST_TYPE).strip() or POST_TYPE
311
+ if post_type != POST_TYPE:
312
+ raise RuntimeError(
313
+ f"Unsupported ALEPH_BOOTSTRAP_POST_TYPE: {post_type}. Expected {POST_TYPE}."
314
+ )
301
315
  api_host = env_values.get("ALEPH_BOOTSTRAP_API_HOST", DEFAULT_API_HOST).strip() or DEFAULT_API_HOST
302
316
 
303
317
  hashes = fetch_current_hashes(
@@ -25,9 +25,7 @@ DESCRIBE_SCRIPT = os.environ.get("DESCRIBE_SCRIPT", "/usr/local/sbin/uc-go-peer-
25
25
  DEFAULT_API_HOST = os.environ.get("ALEPH_BOOTSTRAP_API_HOST", "https://api.aleph.im")
26
26
  DEFAULT_CHANNEL = os.environ.get("ALEPH_BOOTSTRAP_CHANNEL", "simple-todo")
27
27
  DEFAULT_REF = os.environ.get("ALEPH_BOOTSTRAP_REF", "simple-todo-bootstrap")
28
- COMPACT_POST_TYPE = "relay-bootstrap-v2"
29
- LEGACY_POST_TYPE = "relay-bootstrap"
30
- DEFAULT_POST_TYPE = os.environ.get("ALEPH_BOOTSTRAP_POST_TYPE", COMPACT_POST_TYPE)
28
+ POST_TYPE = "relay-bootstrap-v2"
31
29
  DEFAULT_PROFILE = os.environ.get("ALEPH_BOOTSTRAP_PROFILE", "uc-go-peer")
32
30
  MAX_PREVIOUS_PAGES = int(os.environ.get("ALEPH_BOOTSTRAP_MAX_PREVIOUS_PAGES", "5"))
33
31
  PAGINATION = int(os.environ.get("ALEPH_BOOTSTRAP_PAGINATION", "50"))
@@ -72,10 +70,9 @@ def has_peer_id(addr: str) -> bool:
72
70
 
73
71
  def is_browser_dialable(addr: str) -> bool:
74
72
  normalized = addr.lower()
75
- return any(
76
- token in normalized
77
- for token in ("/ws", "/wss", "/webtransport", "/webrtc-direct")
78
- )
73
+ if "/webtransport" in normalized or "/webrtc-direct" in normalized:
74
+ return "/certhash/" in normalized
75
+ return "/ws" in normalized or "/wss" in normalized
79
76
 
80
77
 
81
78
  def is_public_multiaddr(addr: str) -> bool:
@@ -442,6 +439,15 @@ def parse_post_record(entry: object) -> dict[str, object] | None:
442
439
  item_content = None
443
440
 
444
441
  content_source = item_content if isinstance(item_content, dict) else entry
442
+ record_type = content_source.get("type")
443
+ if record_type == "relay-bootstrap":
444
+ raise RuntimeError(
445
+ "Legacy relay-bootstrap record encountered. Only relay-bootstrap-v2 is supported."
446
+ )
447
+ if isinstance(record_type, str) and record_type and record_type != POST_TYPE:
448
+ raise RuntimeError(
449
+ f"Unsupported relay bootstrap post type: {record_type}. Expected {POST_TYPE}."
450
+ )
445
451
  content = content_source.get("content")
446
452
  if not isinstance(content, dict):
447
453
  return None
@@ -477,7 +483,7 @@ def fetch_previous_hashes(
477
483
  api_host: str,
478
484
  channel: str,
479
485
  ref: str,
480
- post_types: list[str],
486
+ post_type: str,
481
487
  sender: str,
482
488
  current_item_hash: str,
483
489
  now_ms: int,
@@ -485,38 +491,37 @@ def fetch_previous_hashes(
485
491
  stale_cutoff_ms = now_ms - (STALE_RECORD_MAX_AGE_SECONDS * 1000)
486
492
  normalized_sender = normalize_address(sender)
487
493
  found: list[str] = []
488
- for post_type in post_types:
489
- for page in range(1, MAX_PREVIOUS_PAGES + 1):
490
- url = (
491
- f"{api_host.rstrip('/')}/api/v0/posts.json?"
492
- f"channels={urllib.parse.quote(channel)}&"
493
- f"refs={urllib.parse.quote(ref)}&"
494
- f"types={urllib.parse.quote(post_type)}&"
495
- f"pagination={PAGINATION}&page={page}"
496
- )
497
- payload = get_json(url)
498
- posts = payload.get("posts")
499
- if not isinstance(posts, list):
500
- break
501
-
502
- for entry in posts:
503
- parsed = parse_post_record(entry)
504
- if parsed is None:
505
- continue
506
- same_publisher = normalized_sender in {
507
- normalize_address(parsed["sender"]),
508
- normalize_address(parsed["publisher_address"]),
509
- }
510
- if not same_publisher:
511
- continue
512
- if parsed["item_hash"] == current_item_hash:
513
- continue
514
- updated_at = timestamp_ms(parsed["updated_at"])
515
- if updated_at is not None and updated_at < stale_cutoff_ms:
516
- found.append(str(parsed["item_hash"]))
517
-
518
- if len(posts) < PAGINATION:
519
- break
494
+ for page in range(1, MAX_PREVIOUS_PAGES + 1):
495
+ url = (
496
+ f"{api_host.rstrip('/')}/api/v0/posts.json?"
497
+ f"channels={urllib.parse.quote(channel)}&"
498
+ f"refs={urllib.parse.quote(ref)}&"
499
+ f"types={urllib.parse.quote(post_type)}&"
500
+ f"pagination={PAGINATION}&page={page}"
501
+ )
502
+ payload = get_json(url)
503
+ posts = payload.get("posts")
504
+ if not isinstance(posts, list):
505
+ break
506
+
507
+ for entry in posts:
508
+ parsed = parse_post_record(entry)
509
+ if parsed is None:
510
+ continue
511
+ same_publisher = normalized_sender in {
512
+ normalize_address(parsed["sender"]),
513
+ normalize_address(parsed["publisher_address"]),
514
+ }
515
+ if not same_publisher:
516
+ continue
517
+ if parsed["item_hash"] == current_item_hash:
518
+ continue
519
+ updated_at = timestamp_ms(parsed["updated_at"])
520
+ if updated_at is not None and updated_at < stale_cutoff_ms:
521
+ found.append(str(parsed["item_hash"]))
522
+
523
+ if len(posts) < PAGINATION:
524
+ break
520
525
 
521
526
  return dedupe(found)
522
527
 
@@ -580,18 +585,16 @@ def main() -> None:
580
585
  version = env_values.get("ALEPH_BOOTSTRAP_VERSION", "").strip() or None
581
586
  channel = env_values.get("ALEPH_BOOTSTRAP_CHANNEL", DEFAULT_CHANNEL).strip() or DEFAULT_CHANNEL
582
587
  ref = env_values.get("ALEPH_BOOTSTRAP_REF", DEFAULT_REF).strip() or DEFAULT_REF
583
- post_type = env_values.get("ALEPH_BOOTSTRAP_POST_TYPE", DEFAULT_POST_TYPE).strip() or DEFAULT_POST_TYPE
588
+ post_type = env_values.get("ALEPH_BOOTSTRAP_POST_TYPE", POST_TYPE).strip() or POST_TYPE
589
+ if post_type != POST_TYPE:
590
+ raise RuntimeError(
591
+ f"Unsupported ALEPH_BOOTSTRAP_POST_TYPE: {post_type}. Expected {POST_TYPE}."
592
+ )
584
593
  api_host = env_values.get("ALEPH_BOOTSTRAP_API_HOST", DEFAULT_API_HOST).strip() or DEFAULT_API_HOST
585
- compact = post_type == COMPACT_POST_TYPE
586
- published_multiaddrs = (
587
- select_compact_multiaddrs(browser_multiaddrs or multiaddrs)
588
- if compact
589
- else multiaddrs
590
- )
591
- published_browser_multiaddrs = [] if compact else browser_multiaddrs
594
+ published_multiaddrs = select_compact_multiaddrs(browser_multiaddrs or multiaddrs)
595
+ published_browser_multiaddrs: list[str] = []
592
596
  if not published_multiaddrs:
593
- reason = "no public browser multiaddrs" if compact else "no public multiaddrs"
594
- print(json_dumps({"status": "skipped", "reason": reason}))
597
+ print(json_dumps({"status": "skipped", "reason": "no public browser multiaddrs"}))
595
598
  return
596
599
 
597
600
  owner_authorization = load_owner_authorization(
@@ -646,7 +649,7 @@ def main() -> None:
646
649
  api_host,
647
650
  channel,
648
651
  ref,
649
- [post_type, LEGACY_POST_TYPE] if compact else [post_type],
652
+ post_type,
650
653
  publisher_address,
651
654
  str(unsigned_message["item_hash"]),
652
655
  now_ms,
@@ -1,4 +1,6 @@
1
1
  #!/usr/bin/env python3
2
+ from __future__ import annotations
3
+
2
4
  import json
3
5
  import os
4
6
  import re
@@ -48,6 +50,44 @@ def append_peer_id(addr: str, peer_id: str) -> str:
48
50
  return addr if "/p2p/" in addr else f"{addr}/p2p/{peer_id}"
49
51
 
50
52
 
53
+ def browser_transport_suffixes(listening_addrs: list[str], transport: str) -> list[str]:
54
+ """Return dial-critical transport suffixes advertised by the live libp2p host.
55
+
56
+ WebTransport and WebRTC Direct addresses include a certificate hash generated
57
+ by libp2p. Reconstructing these addresses from only the public IP and port
58
+ drops that hash and produces addresses browsers cannot dial.
59
+ """
60
+ marker = f"/{transport}/"
61
+ suffixes: list[str] = []
62
+ for addr in listening_addrs:
63
+ normalized = addr.lower()
64
+ marker_index = normalized.find(marker)
65
+ if marker_index < 0:
66
+ continue
67
+ suffix = addr[marker_index:]
68
+ if "/certhash/" not in suffix.lower():
69
+ continue
70
+ suffixes.append(suffix)
71
+ return dedupe(suffixes)
72
+
73
+
74
+ def public_udp_multiaddrs(
75
+ public_ipv4: str,
76
+ public_ipv6: str,
77
+ udp_port: str,
78
+ suffixes: list[str],
79
+ peer_id: str,
80
+ ) -> list[str]:
81
+ result: list[str] = []
82
+ for suffix in suffixes:
83
+ suffix_with_peer = append_peer_id(suffix, peer_id)
84
+ if public_ipv4:
85
+ result.append(f"/ip4/{public_ipv4}/udp/{udp_port}{suffix_with_peer}")
86
+ if public_ipv6:
87
+ result.append(f"/ip6/{public_ipv6}/udp/{udp_port}{suffix_with_peer}")
88
+ return dedupe(result)
89
+
90
+
51
91
  def parse_logs() -> tuple[str | None, list[str]]:
52
92
  result = subprocess.run(
53
93
  ["journalctl", "-u", SERVICE_NAME, "-n", "500", "--no-pager"],
@@ -121,12 +161,24 @@ def build_probe_multiaddrs(env_values: dict[str, str], peer_id: str, listening_a
121
161
  public_ipv6 = env_values.get("PUBLIC_IPV6", "").strip()
122
162
  udp_port = env_values.get("EXTERNAL_RELAY_UDP_PORT", "").strip()
123
163
  if udp_port:
124
- if public_ipv4:
125
- webtransport_multiaddrs.append(f"/ip4/{public_ipv4}/udp/{udp_port}/quic-v1/webtransport/p2p/{peer_id}")
126
- webrtc_direct_multiaddrs.append(f"/ip4/{public_ipv4}/udp/{udp_port}/webrtc-direct/p2p/{peer_id}")
127
- if public_ipv6:
128
- webtransport_multiaddrs.append(f"/ip6/{public_ipv6}/udp/{udp_port}/quic-v1/webtransport/p2p/{peer_id}")
129
- webrtc_direct_multiaddrs.append(f"/ip6/{public_ipv6}/udp/{udp_port}/webrtc-direct/p2p/{peer_id}")
164
+ webtransport_multiaddrs.extend(
165
+ public_udp_multiaddrs(
166
+ public_ipv4,
167
+ public_ipv6,
168
+ udp_port,
169
+ browser_transport_suffixes(listening_addrs, "quic-v1/webtransport"),
170
+ peer_id,
171
+ )
172
+ )
173
+ webrtc_direct_multiaddrs.extend(
174
+ public_udp_multiaddrs(
175
+ public_ipv4,
176
+ public_ipv6,
177
+ udp_port,
178
+ browser_transport_suffixes(listening_addrs, "webrtc-direct"),
179
+ peer_id,
180
+ )
181
+ )
130
182
 
131
183
  probe_multiaddrs.extend(direct_tcp_multiaddrs)
132
184
  probe_multiaddrs.extend(autotls_multiaddrs)