@le-space/rootfs 0.6.4 → 0.6.6
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/rootfs/orbitdb-relay-bootstrap-refresh.py +128 -48
- package/reference/uc-go-peer/rootfs/uc-go-peer-bootstrap-refresh.py +107 -48
- package/reference/ucan-store/rootfs/ucan-store-configure.sh +76 -4
- package/reference/ucan-store/rootfs/ucan-store-request-guard.mjs +23 -7
- package/reference/ucan-store/rootfs/ucan-store-setup-server.py +6 -0
package/package.json
CHANGED
|
@@ -27,10 +27,16 @@ 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
|
-
|
|
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)
|
|
31
33
|
DEFAULT_PROFILE = os.environ.get("ALEPH_BOOTSTRAP_PROFILE", "orbitdb-relay")
|
|
32
34
|
MAX_PREVIOUS_PAGES = int(os.environ.get("ALEPH_BOOTSTRAP_MAX_PREVIOUS_PAGES", "5"))
|
|
33
35
|
PAGINATION = int(os.environ.get("ALEPH_BOOTSTRAP_PAGINATION", "50"))
|
|
36
|
+
COMPACT_MULTIADDR_LIMIT = int(os.environ.get("ALEPH_BOOTSTRAP_COMPACT_MULTIADDR_LIMIT", "3"))
|
|
37
|
+
STALE_RECORD_MAX_AGE_SECONDS = int(
|
|
38
|
+
os.environ.get("ALEPH_BOOTSTRAP_STALE_RECORD_MAX_AGE_SECONDS", str(6 * 60 * 60))
|
|
39
|
+
)
|
|
34
40
|
|
|
35
41
|
|
|
36
42
|
def parse_env_file(path: str) -> dict[str, str]:
|
|
@@ -112,6 +118,49 @@ def filter_public_multiaddrs(values: list[str], browser_only: bool = False) -> l
|
|
|
112
118
|
return dedupe(filtered)
|
|
113
119
|
|
|
114
120
|
|
|
121
|
+
def browser_multiaddr_preference(addr: str) -> int:
|
|
122
|
+
normalized = addr.lower()
|
|
123
|
+
if "/tcp/443/tls/ws/" in normalized:
|
|
124
|
+
return 0
|
|
125
|
+
if "/tls/ws/" in normalized:
|
|
126
|
+
return 1
|
|
127
|
+
if "/webtransport/" in normalized:
|
|
128
|
+
return 2
|
|
129
|
+
if "/webrtc-direct/" in normalized:
|
|
130
|
+
return 3
|
|
131
|
+
if "/ws" in normalized:
|
|
132
|
+
return 4
|
|
133
|
+
return 5
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def browser_multiaddr_transport(addr: str) -> str:
|
|
137
|
+
normalized = addr.lower()
|
|
138
|
+
if "/webtransport/" in normalized:
|
|
139
|
+
return "webtransport"
|
|
140
|
+
if "/webrtc-direct/" in normalized:
|
|
141
|
+
return "webrtc"
|
|
142
|
+
if "/ws" in normalized or "/wss" in normalized:
|
|
143
|
+
return "websocket"
|
|
144
|
+
return "other"
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def select_compact_multiaddrs(values: list[str]) -> list[str]:
|
|
148
|
+
public_browser_addrs = filter_public_multiaddrs(values, browser_only=True)
|
|
149
|
+
per_transport_limit = max(1, COMPACT_MULTIADDR_LIMIT)
|
|
150
|
+
selected_by_transport: dict[str, int] = {}
|
|
151
|
+
result: list[str] = []
|
|
152
|
+
|
|
153
|
+
for addr in sorted(public_browser_addrs, key=browser_multiaddr_preference):
|
|
154
|
+
transport = browser_multiaddr_transport(addr)
|
|
155
|
+
selected = selected_by_transport.get(transport, 0)
|
|
156
|
+
if selected >= per_transport_limit:
|
|
157
|
+
continue
|
|
158
|
+
selected_by_transport[transport] = selected + 1
|
|
159
|
+
result.append(addr)
|
|
160
|
+
|
|
161
|
+
return result
|
|
162
|
+
|
|
163
|
+
|
|
115
164
|
def json_dumps(payload: object) -> str:
|
|
116
165
|
return json.dumps(payload, separators=(",", ":"))
|
|
117
166
|
|
|
@@ -339,60 +388,82 @@ def parse_post_record(entry: object) -> dict[str, object] | None:
|
|
|
339
388
|
except json.JSONDecodeError:
|
|
340
389
|
item_content = None
|
|
341
390
|
|
|
342
|
-
if
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
content = item_content.get("content")
|
|
391
|
+
content_source = item_content if isinstance(item_content, dict) else entry
|
|
392
|
+
content = content_source.get("content")
|
|
346
393
|
if not isinstance(content, dict):
|
|
347
394
|
return None
|
|
348
395
|
|
|
349
396
|
return {
|
|
350
397
|
"item_hash": item_hash,
|
|
351
398
|
"sender": sender,
|
|
399
|
+
"publisher_address": content.get("publisherAddress"),
|
|
352
400
|
"registration_id": content.get("registrationId"),
|
|
401
|
+
"peer_id": content.get("peerId"),
|
|
402
|
+
"profile": content.get("profile"),
|
|
403
|
+
"updated_at": content.get("updatedAt"),
|
|
353
404
|
}
|
|
354
405
|
|
|
355
406
|
|
|
407
|
+
def normalize_address(value: object) -> str:
|
|
408
|
+
return str(value or "").strip().lower()
|
|
409
|
+
|
|
410
|
+
|
|
411
|
+
def timestamp_ms(value: object) -> int | None:
|
|
412
|
+
if isinstance(value, bool):
|
|
413
|
+
return None
|
|
414
|
+
if isinstance(value, (int, float)):
|
|
415
|
+
return int(value)
|
|
416
|
+
if isinstance(value, str):
|
|
417
|
+
stripped = value.strip()
|
|
418
|
+
if stripped.isdigit():
|
|
419
|
+
return int(stripped)
|
|
420
|
+
return None
|
|
421
|
+
|
|
422
|
+
|
|
356
423
|
def fetch_previous_hashes(
|
|
357
424
|
api_host: str,
|
|
358
425
|
channel: str,
|
|
359
426
|
ref: str,
|
|
360
|
-
|
|
427
|
+
post_types: list[str],
|
|
361
428
|
sender: str,
|
|
362
|
-
registration_id: str | None,
|
|
363
429
|
current_item_hash: str,
|
|
430
|
+
now_ms: int,
|
|
364
431
|
) -> list[str]:
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
432
|
+
stale_cutoff_ms = now_ms - (STALE_RECORD_MAX_AGE_SECONDS * 1000)
|
|
433
|
+
normalized_sender = normalize_address(sender)
|
|
368
434
|
found: list[str] = []
|
|
369
|
-
for
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
435
|
+
for post_type in post_types:
|
|
436
|
+
for page in range(1, MAX_PREVIOUS_PAGES + 1):
|
|
437
|
+
url = (
|
|
438
|
+
f"{api_host.rstrip('/')}/api/v0/posts.json?"
|
|
439
|
+
f"channels={urllib.parse.quote(channel)}&"
|
|
440
|
+
f"refs={urllib.parse.quote(ref)}&"
|
|
441
|
+
f"types={urllib.parse.quote(post_type)}&"
|
|
442
|
+
f"pagination={PAGINATION}&page={page}"
|
|
443
|
+
)
|
|
444
|
+
payload = get_json(url)
|
|
445
|
+
posts = payload.get("posts")
|
|
446
|
+
if not isinstance(posts, list):
|
|
447
|
+
break
|
|
448
|
+
|
|
449
|
+
for entry in posts:
|
|
450
|
+
parsed = parse_post_record(entry)
|
|
451
|
+
if parsed is None:
|
|
452
|
+
continue
|
|
453
|
+
same_publisher = normalized_sender in {
|
|
454
|
+
normalize_address(parsed["sender"]),
|
|
455
|
+
normalize_address(parsed["publisher_address"]),
|
|
456
|
+
}
|
|
457
|
+
if not same_publisher:
|
|
458
|
+
continue
|
|
459
|
+
if parsed["item_hash"] == current_item_hash:
|
|
460
|
+
continue
|
|
461
|
+
updated_at = timestamp_ms(parsed["updated_at"])
|
|
462
|
+
if updated_at is not None and updated_at < stale_cutoff_ms:
|
|
463
|
+
found.append(str(parsed["item_hash"]))
|
|
464
|
+
|
|
465
|
+
if len(posts) < PAGINATION:
|
|
466
|
+
break
|
|
396
467
|
|
|
397
468
|
return dedupe(found)
|
|
398
469
|
|
|
@@ -448,9 +519,6 @@ def main() -> None:
|
|
|
448
519
|
browser_multiaddrs = filter_public_multiaddrs(
|
|
449
520
|
metadata.get("browser_bootstrap_multiaddrs") or [], browser_only=True
|
|
450
521
|
)
|
|
451
|
-
if not multiaddrs:
|
|
452
|
-
print(json_dumps({"status": "skipped", "reason": "no public multiaddrs"}))
|
|
453
|
-
return
|
|
454
522
|
|
|
455
523
|
publisher_address = address_from_private_key(publisher_private_key)
|
|
456
524
|
now_ms = int(time.time() * 1000)
|
|
@@ -461,6 +529,17 @@ def main() -> None:
|
|
|
461
529
|
ref = env_values.get("ALEPH_BOOTSTRAP_REF", DEFAULT_REF).strip() or DEFAULT_REF
|
|
462
530
|
post_type = env_values.get("ALEPH_BOOTSTRAP_POST_TYPE", DEFAULT_POST_TYPE).strip() or DEFAULT_POST_TYPE
|
|
463
531
|
api_host = env_values.get("ALEPH_BOOTSTRAP_API_HOST", DEFAULT_API_HOST).strip() or DEFAULT_API_HOST
|
|
532
|
+
compact = post_type == COMPACT_POST_TYPE
|
|
533
|
+
published_multiaddrs = (
|
|
534
|
+
select_compact_multiaddrs(browser_multiaddrs or multiaddrs)
|
|
535
|
+
if compact
|
|
536
|
+
else multiaddrs
|
|
537
|
+
)
|
|
538
|
+
published_browser_multiaddrs = [] if compact else browser_multiaddrs
|
|
539
|
+
if not published_multiaddrs:
|
|
540
|
+
reason = "no public browser multiaddrs" if compact else "no public multiaddrs"
|
|
541
|
+
print(json_dumps({"status": "skipped", "reason": reason}))
|
|
542
|
+
return
|
|
464
543
|
|
|
465
544
|
owner_authorization = load_owner_authorization(
|
|
466
545
|
env_values,
|
|
@@ -477,8 +556,8 @@ def main() -> None:
|
|
|
477
556
|
|
|
478
557
|
proof_payload = relay_proof_payload(
|
|
479
558
|
peer_id,
|
|
480
|
-
|
|
481
|
-
|
|
559
|
+
published_multiaddrs,
|
|
560
|
+
published_browser_multiaddrs,
|
|
482
561
|
registration_id,
|
|
483
562
|
profile,
|
|
484
563
|
version,
|
|
@@ -492,8 +571,8 @@ def main() -> None:
|
|
|
492
571
|
post_content = build_post_content(
|
|
493
572
|
publisher_address,
|
|
494
573
|
peer_id,
|
|
495
|
-
|
|
496
|
-
|
|
574
|
+
published_multiaddrs,
|
|
575
|
+
published_browser_multiaddrs,
|
|
497
576
|
registration_id,
|
|
498
577
|
profile,
|
|
499
578
|
version,
|
|
@@ -514,10 +593,10 @@ def main() -> None:
|
|
|
514
593
|
api_host,
|
|
515
594
|
channel,
|
|
516
595
|
ref,
|
|
517
|
-
post_type,
|
|
596
|
+
[post_type, LEGACY_POST_TYPE] if compact else [post_type],
|
|
518
597
|
publisher_address,
|
|
519
|
-
registration_id,
|
|
520
598
|
str(unsigned_message["item_hash"]),
|
|
599
|
+
now_ms,
|
|
521
600
|
)
|
|
522
601
|
forget_response = broadcast_forget(
|
|
523
602
|
api_host, publisher_address, publisher_private_key, previous_hashes, channel
|
|
@@ -531,9 +610,10 @@ def main() -> None:
|
|
|
531
610
|
"itemHash": unsigned_message["item_hash"],
|
|
532
611
|
"sender": publisher_address,
|
|
533
612
|
"peerId": peer_id,
|
|
534
|
-
"publishedMultiaddrs":
|
|
535
|
-
"publishedBrowserMultiaddrs":
|
|
613
|
+
"publishedMultiaddrs": published_multiaddrs,
|
|
614
|
+
"publishedBrowserMultiaddrs": published_browser_multiaddrs or published_multiaddrs,
|
|
536
615
|
"forgottenHashes": previous_hashes,
|
|
616
|
+
"forgetStaleOlderThanSeconds": STALE_RECORD_MAX_AGE_SECONDS,
|
|
537
617
|
"forgetResponse": forget_response[1] if forget_response else None,
|
|
538
618
|
"response": response,
|
|
539
619
|
}
|
|
@@ -25,10 +25,16 @@ 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
|
-
|
|
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)
|
|
29
31
|
DEFAULT_PROFILE = os.environ.get("ALEPH_BOOTSTRAP_PROFILE", "uc-go-peer")
|
|
30
32
|
MAX_PREVIOUS_PAGES = int(os.environ.get("ALEPH_BOOTSTRAP_MAX_PREVIOUS_PAGES", "5"))
|
|
31
33
|
PAGINATION = int(os.environ.get("ALEPH_BOOTSTRAP_PAGINATION", "50"))
|
|
34
|
+
COMPACT_MULTIADDR_LIMIT = int(os.environ.get("ALEPH_BOOTSTRAP_COMPACT_MULTIADDR_LIMIT", "3"))
|
|
35
|
+
STALE_RECORD_MAX_AGE_SECONDS = int(
|
|
36
|
+
os.environ.get("ALEPH_BOOTSTRAP_STALE_RECORD_MAX_AGE_SECONDS", str(6 * 60 * 60))
|
|
37
|
+
)
|
|
32
38
|
|
|
33
39
|
|
|
34
40
|
def parse_env_file(path: str) -> dict[str, str]:
|
|
@@ -110,6 +116,28 @@ def filter_public_multiaddrs(values: list[str], browser_only: bool = False) -> l
|
|
|
110
116
|
return dedupe(filtered)
|
|
111
117
|
|
|
112
118
|
|
|
119
|
+
def browser_multiaddr_preference(addr: str) -> int:
|
|
120
|
+
normalized = addr.lower()
|
|
121
|
+
if "/tcp/443/tls/ws/" in normalized:
|
|
122
|
+
return 0
|
|
123
|
+
if "/tls/ws/" in normalized:
|
|
124
|
+
return 1
|
|
125
|
+
if "/webtransport/" in normalized:
|
|
126
|
+
return 2
|
|
127
|
+
if "/webrtc-direct/" in normalized:
|
|
128
|
+
return 3
|
|
129
|
+
if "/ws" in normalized:
|
|
130
|
+
return 4
|
|
131
|
+
return 5
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def select_compact_multiaddrs(values: list[str]) -> list[str]:
|
|
135
|
+
public_browser_addrs = filter_public_multiaddrs(values, browser_only=True)
|
|
136
|
+
return sorted(public_browser_addrs, key=browser_multiaddr_preference)[
|
|
137
|
+
: max(1, COMPACT_MULTIADDR_LIMIT)
|
|
138
|
+
]
|
|
139
|
+
|
|
140
|
+
|
|
113
141
|
def json_dumps(payload: object) -> str:
|
|
114
142
|
return json.dumps(payload, separators=(",", ":"))
|
|
115
143
|
|
|
@@ -413,60 +441,82 @@ def parse_post_record(entry: object) -> dict[str, object] | None:
|
|
|
413
441
|
except json.JSONDecodeError:
|
|
414
442
|
item_content = None
|
|
415
443
|
|
|
416
|
-
if
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
content = item_content.get("content")
|
|
444
|
+
content_source = item_content if isinstance(item_content, dict) else entry
|
|
445
|
+
content = content_source.get("content")
|
|
420
446
|
if not isinstance(content, dict):
|
|
421
447
|
return None
|
|
422
448
|
|
|
423
449
|
return {
|
|
424
450
|
"item_hash": item_hash,
|
|
425
451
|
"sender": sender,
|
|
452
|
+
"publisher_address": content.get("publisherAddress"),
|
|
426
453
|
"registration_id": content.get("registrationId"),
|
|
454
|
+
"peer_id": content.get("peerId"),
|
|
455
|
+
"profile": content.get("profile"),
|
|
456
|
+
"updated_at": content.get("updatedAt"),
|
|
427
457
|
}
|
|
428
458
|
|
|
429
459
|
|
|
460
|
+
def normalize_address(value: object) -> str:
|
|
461
|
+
return str(value or "").strip().lower()
|
|
462
|
+
|
|
463
|
+
|
|
464
|
+
def timestamp_ms(value: object) -> int | None:
|
|
465
|
+
if isinstance(value, bool):
|
|
466
|
+
return None
|
|
467
|
+
if isinstance(value, (int, float)):
|
|
468
|
+
return int(value)
|
|
469
|
+
if isinstance(value, str):
|
|
470
|
+
stripped = value.strip()
|
|
471
|
+
if stripped.isdigit():
|
|
472
|
+
return int(stripped)
|
|
473
|
+
return None
|
|
474
|
+
|
|
475
|
+
|
|
430
476
|
def fetch_previous_hashes(
|
|
431
477
|
api_host: str,
|
|
432
478
|
channel: str,
|
|
433
479
|
ref: str,
|
|
434
|
-
|
|
480
|
+
post_types: list[str],
|
|
435
481
|
sender: str,
|
|
436
|
-
registration_id: str | None,
|
|
437
482
|
current_item_hash: str,
|
|
483
|
+
now_ms: int,
|
|
438
484
|
) -> list[str]:
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
485
|
+
stale_cutoff_ms = now_ms - (STALE_RECORD_MAX_AGE_SECONDS * 1000)
|
|
486
|
+
normalized_sender = normalize_address(sender)
|
|
442
487
|
found: list[str] = []
|
|
443
|
-
for
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
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
|
|
470
520
|
|
|
471
521
|
return dedupe(found)
|
|
472
522
|
|
|
@@ -522,9 +572,6 @@ def main() -> None:
|
|
|
522
572
|
browser_multiaddrs = filter_public_multiaddrs(
|
|
523
573
|
metadata.get("browser_bootstrap_multiaddrs") or [], browser_only=True
|
|
524
574
|
)
|
|
525
|
-
if not multiaddrs:
|
|
526
|
-
print(json_dumps({"status": "skipped", "reason": "no public multiaddrs"}))
|
|
527
|
-
return
|
|
528
575
|
|
|
529
576
|
publisher_address = address_from_private_key(publisher_private_key)
|
|
530
577
|
now_ms = int(time.time() * 1000)
|
|
@@ -535,6 +582,17 @@ def main() -> None:
|
|
|
535
582
|
ref = env_values.get("ALEPH_BOOTSTRAP_REF", DEFAULT_REF).strip() or DEFAULT_REF
|
|
536
583
|
post_type = env_values.get("ALEPH_BOOTSTRAP_POST_TYPE", DEFAULT_POST_TYPE).strip() or DEFAULT_POST_TYPE
|
|
537
584
|
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
|
|
592
|
+
if not published_multiaddrs:
|
|
593
|
+
reason = "no public browser multiaddrs" if compact else "no public multiaddrs"
|
|
594
|
+
print(json_dumps({"status": "skipped", "reason": reason}))
|
|
595
|
+
return
|
|
538
596
|
|
|
539
597
|
owner_authorization = load_owner_authorization(
|
|
540
598
|
env_values,
|
|
@@ -549,8 +607,8 @@ def main() -> None:
|
|
|
549
607
|
|
|
550
608
|
proof_payload = relay_proof_payload(
|
|
551
609
|
peer_id,
|
|
552
|
-
|
|
553
|
-
|
|
610
|
+
published_multiaddrs,
|
|
611
|
+
published_browser_multiaddrs,
|
|
554
612
|
registration_id,
|
|
555
613
|
profile,
|
|
556
614
|
version,
|
|
@@ -564,8 +622,8 @@ def main() -> None:
|
|
|
564
622
|
post_content = build_post_content(
|
|
565
623
|
publisher_address,
|
|
566
624
|
peer_id,
|
|
567
|
-
|
|
568
|
-
|
|
625
|
+
published_multiaddrs,
|
|
626
|
+
published_browser_multiaddrs,
|
|
569
627
|
registration_id,
|
|
570
628
|
profile,
|
|
571
629
|
version,
|
|
@@ -588,10 +646,10 @@ def main() -> None:
|
|
|
588
646
|
api_host,
|
|
589
647
|
channel,
|
|
590
648
|
ref,
|
|
591
|
-
post_type,
|
|
649
|
+
[post_type, LEGACY_POST_TYPE] if compact else [post_type],
|
|
592
650
|
publisher_address,
|
|
593
|
-
registration_id,
|
|
594
651
|
str(unsigned_message["item_hash"]),
|
|
652
|
+
now_ms,
|
|
595
653
|
)
|
|
596
654
|
forget_response = broadcast_forget(
|
|
597
655
|
api_host, publisher_address, publisher_private_key, previous_hashes, channel
|
|
@@ -605,9 +663,10 @@ def main() -> None:
|
|
|
605
663
|
"itemHash": unsigned_message["item_hash"],
|
|
606
664
|
"sender": publisher_address,
|
|
607
665
|
"peerId": peer_id,
|
|
608
|
-
"publishedMultiaddrs":
|
|
609
|
-
"publishedBrowserMultiaddrs":
|
|
666
|
+
"publishedMultiaddrs": published_multiaddrs,
|
|
667
|
+
"publishedBrowserMultiaddrs": published_browser_multiaddrs or published_multiaddrs,
|
|
610
668
|
"forgottenHashes": previous_hashes,
|
|
669
|
+
"forgetStaleOlderThanSeconds": STALE_RECORD_MAX_AGE_SECONDS,
|
|
611
670
|
"ownerAuthorizationPresent": owner_authorization is not None,
|
|
612
671
|
"forgetResponse": forget_response[1] if forget_response else None,
|
|
613
672
|
"response": response,
|
|
@@ -16,8 +16,10 @@ SERVICE_GROUP="${SERVICE_GROUP:-ucan-store}"
|
|
|
16
16
|
PUBLIC_IPV4=""
|
|
17
17
|
PUBLIC_IPV6=""
|
|
18
18
|
PROXY_HOSTNAME=""
|
|
19
|
+
SERVICE_HOSTNAME=""
|
|
19
20
|
SERVICE_DID=""
|
|
20
21
|
SERVICE_ORIGIN=""
|
|
22
|
+
PUBLIC_STORAGE_ORIGIN=""
|
|
21
23
|
WEBAUTHN_ORIGIN=""
|
|
22
24
|
WEBAUTHN_ORIGIN_FALLBACKS=""
|
|
23
25
|
ADMIN_DID=""
|
|
@@ -34,6 +36,7 @@ Usage:
|
|
|
34
36
|
[--proxy-hostname <hostname>] \
|
|
35
37
|
[--service-did <did>] \
|
|
36
38
|
[--service-origin <origin>] \
|
|
39
|
+
[--public-storage-origin <origin>] \
|
|
37
40
|
[--webauthn-origin <origin>] \
|
|
38
41
|
[--webauthn-origin-fallbacks <csv>] \
|
|
39
42
|
[--admin-did <did>] \
|
|
@@ -72,19 +75,67 @@ PY
|
|
|
72
75
|
}
|
|
73
76
|
|
|
74
77
|
write_caddyfile() {
|
|
75
|
-
local
|
|
78
|
+
local hostnames=()
|
|
79
|
+
local hostname
|
|
80
|
+
local known
|
|
81
|
+
for hostname in "$@"; do
|
|
82
|
+
if [ -z "${hostname}" ]; then
|
|
83
|
+
continue
|
|
84
|
+
fi
|
|
85
|
+
known="0"
|
|
86
|
+
if [ "${#hostnames[@]}" -gt 0 ]; then
|
|
87
|
+
for existing in "${hostnames[@]}"; do
|
|
88
|
+
if [ "${existing}" = "${hostname}" ]; then
|
|
89
|
+
known="1"
|
|
90
|
+
break
|
|
91
|
+
fi
|
|
92
|
+
done
|
|
93
|
+
fi
|
|
94
|
+
if [ "${known}" = "0" ]; then
|
|
95
|
+
hostnames+=("${hostname}")
|
|
96
|
+
fi
|
|
97
|
+
done
|
|
98
|
+
|
|
99
|
+
if [ "${#hostnames[@]}" -eq 0 ]; then
|
|
100
|
+
return 0
|
|
101
|
+
fi
|
|
102
|
+
|
|
103
|
+
local site_label
|
|
104
|
+
local index
|
|
105
|
+
site_label="${hostnames[0]}"
|
|
106
|
+
for ((index = 1; index < ${#hostnames[@]}; index++)); do
|
|
107
|
+
site_label+=", ${hostnames[${index}]}"
|
|
108
|
+
done
|
|
76
109
|
mkdir -p "$(dirname "${CADDYFILE}")"
|
|
77
110
|
cat > "${CADDYFILE}" <<EOF
|
|
78
111
|
{
|
|
79
112
|
auto_https disable_redirects
|
|
80
113
|
}
|
|
81
114
|
|
|
82
|
-
${
|
|
115
|
+
${site_label} {
|
|
83
116
|
reverse_proxy 127.0.0.1:${PROXY_PORT}
|
|
84
117
|
}
|
|
85
118
|
EOF
|
|
86
119
|
}
|
|
87
120
|
|
|
121
|
+
derive_hostname_from_origin() {
|
|
122
|
+
python3 - "${1:-}" <<'PY'
|
|
123
|
+
import sys
|
|
124
|
+
from urllib.parse import urlsplit
|
|
125
|
+
|
|
126
|
+
raw = (sys.argv[1] or "").strip()
|
|
127
|
+
if not raw:
|
|
128
|
+
raise SystemExit(0)
|
|
129
|
+
|
|
130
|
+
parsed = urlsplit(raw)
|
|
131
|
+
host = (parsed.hostname or "").strip().lower()
|
|
132
|
+
if not host:
|
|
133
|
+
raise SystemExit(0)
|
|
134
|
+
|
|
135
|
+
print(host)
|
|
136
|
+
PY
|
|
137
|
+
}
|
|
138
|
+
|
|
88
139
|
derive_service_did_from_hostname() {
|
|
89
140
|
python3 - "${1:-}" <<'PY'
|
|
90
141
|
import sys
|
|
@@ -148,7 +199,15 @@ install_bootstrap_package() {
|
|
|
148
199
|
else
|
|
149
200
|
chmod 0644 "${BOOTSTRAP_PACKAGE_FILE}"
|
|
150
201
|
fi
|
|
151
|
-
|
|
202
|
+
install_bootstrap_verification_permissions
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
install_bootstrap_verification_permissions() {
|
|
206
|
+
if chgrp "${SERVICE_GROUP}" "${BOOTSTRAP_VERIFICATION_FILE}" 2>/dev/null; then
|
|
207
|
+
chmod 0664 "${BOOTSTRAP_VERIFICATION_FILE}"
|
|
208
|
+
else
|
|
209
|
+
chmod 0666 "${BOOTSTRAP_VERIFICATION_FILE}"
|
|
210
|
+
fi
|
|
152
211
|
}
|
|
153
212
|
|
|
154
213
|
while [ "$#" -gt 0 ]; do
|
|
@@ -173,6 +232,10 @@ while [ "$#" -gt 0 ]; do
|
|
|
173
232
|
SERVICE_ORIGIN="${2:-}"
|
|
174
233
|
shift 2
|
|
175
234
|
;;
|
|
235
|
+
--public-storage-origin)
|
|
236
|
+
PUBLIC_STORAGE_ORIGIN="${2:-}"
|
|
237
|
+
shift 2
|
|
238
|
+
;;
|
|
176
239
|
--webauthn-origin)
|
|
177
240
|
WEBAUTHN_ORIGIN="${2:-}"
|
|
178
241
|
shift 2
|
|
@@ -214,6 +277,7 @@ if [ -z "${PUBLIC_IPV4}" ]; then
|
|
|
214
277
|
exit 1
|
|
215
278
|
fi
|
|
216
279
|
SERVICE_ORIGIN="${SERVICE_ORIGIN%/}"
|
|
280
|
+
PUBLIC_STORAGE_ORIGIN="${PUBLIC_STORAGE_ORIGIN%/}"
|
|
217
281
|
|
|
218
282
|
touch "${ENV_FILE}"
|
|
219
283
|
|
|
@@ -242,17 +306,24 @@ fi
|
|
|
242
306
|
if [ -z "${SERVICE_ORIGIN}" ] && [ -n "${PROXY_HOSTNAME}" ]; then
|
|
243
307
|
SERVICE_ORIGIN="https://${PROXY_HOSTNAME}"
|
|
244
308
|
fi
|
|
309
|
+
SERVICE_HOSTNAME="$(derive_hostname_from_origin "${SERVICE_ORIGIN}")"
|
|
245
310
|
|
|
246
311
|
write_env_var "UCAN_STORE_SERVICE_DID" "${SERVICE_DID}"
|
|
312
|
+
write_env_var "UCAN_STORE_SERVICE_HOSTNAME" "${SERVICE_HOSTNAME}"
|
|
247
313
|
|
|
248
314
|
if [ -n "${SERVICE_ORIGIN}" ]; then
|
|
315
|
+
if [ -z "${PUBLIC_STORAGE_ORIGIN}" ]; then
|
|
316
|
+
PUBLIC_STORAGE_ORIGIN="${SERVICE_ORIGIN}"
|
|
317
|
+
fi
|
|
318
|
+
write_env_var "UCAN_STORE_SERVICE_ORIGIN" "${SERVICE_ORIGIN}"
|
|
319
|
+
write_env_var "UCAN_STORE_PUBLIC_STORAGE_ORIGIN" "${PUBLIC_STORAGE_ORIGIN}"
|
|
249
320
|
write_env_var "PUBLIC_UPLOAD_SERVICE_URL" "${SERVICE_ORIGIN}"
|
|
250
321
|
write_env_var "PUBLIC_REVOCATION_URL" "${SERVICE_ORIGIN}"
|
|
251
322
|
write_env_var "PUBLIC_RECEIPTS_URL" "${SERVICE_ORIGIN}/receipt/"
|
|
252
323
|
fi
|
|
253
324
|
|
|
254
325
|
if [ -n "${PROXY_HOSTNAME}" ]; then
|
|
255
|
-
write_caddyfile "${PROXY_HOSTNAME}"
|
|
326
|
+
write_caddyfile "${PROXY_HOSTNAME}" "${SERVICE_HOSTNAME}"
|
|
256
327
|
fi
|
|
257
328
|
|
|
258
329
|
touch "${READY_FILE}"
|
|
@@ -284,5 +355,6 @@ if [ "${START_SERVICE}" = "1" ]; then
|
|
|
284
355
|
--runtime-service-origin "${SERVICE_ORIGIN}" \
|
|
285
356
|
--admin-did "${ADMIN_DID}" \
|
|
286
357
|
--summary-file "${BOOTSTRAP_VERIFICATION_FILE}" >/dev/null
|
|
358
|
+
install_bootstrap_verification_permissions
|
|
287
359
|
fi
|
|
288
360
|
fi
|
|
@@ -23,6 +23,7 @@ const DEFAULT_REVOCATION_URL =
|
|
|
23
23
|
process.env.PUBLIC_REVOCATION_URL || '';
|
|
24
24
|
const DEFAULT_RECEIPTS_URL =
|
|
25
25
|
process.env.PUBLIC_RECEIPTS_URL || '';
|
|
26
|
+
const PROTOCOL_CAPABILITIES = new Set(['ucan/conclude', 'filecoin/offer']);
|
|
26
27
|
|
|
27
28
|
function printHelp() {
|
|
28
29
|
console.log(`Usage:
|
|
@@ -144,6 +145,10 @@ function capabilityCovers(provided, required) {
|
|
|
144
145
|
return false;
|
|
145
146
|
}
|
|
146
147
|
|
|
148
|
+
function isProtocolCapability(capability) {
|
|
149
|
+
return PROTOCOL_CAPABILITIES.has(capability);
|
|
150
|
+
}
|
|
151
|
+
|
|
147
152
|
function isTruthy(value) {
|
|
148
153
|
return ['1', 'true', 'yes', 'on'].includes(String(value || '').trim().toLowerCase());
|
|
149
154
|
}
|
|
@@ -511,8 +516,8 @@ async function proxyRequest(req, res, config, bodyBuffer = null) {
|
|
|
511
516
|
res.setHeader(key, value);
|
|
512
517
|
}
|
|
513
518
|
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
514
|
-
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
|
|
515
|
-
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
|
|
519
|
+
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, OPTIONS');
|
|
520
|
+
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-Amz-Checksum-Sha256');
|
|
516
521
|
const arrayBuffer = await response.arrayBuffer();
|
|
517
522
|
res.end(Buffer.from(arrayBuffer));
|
|
518
523
|
}
|
|
@@ -523,8 +528,8 @@ function sendJson(res, status, payload) {
|
|
|
523
528
|
res.setHeader('Content-Type', 'application/json');
|
|
524
529
|
res.setHeader('Content-Length', String(body.length));
|
|
525
530
|
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
526
|
-
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
|
|
527
|
-
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
|
|
531
|
+
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, OPTIONS');
|
|
532
|
+
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-Amz-Checksum-Sha256');
|
|
528
533
|
res.end(body);
|
|
529
534
|
}
|
|
530
535
|
|
|
@@ -549,6 +554,14 @@ async function enforceRequestPolicy(bodyBuffer, req, config) {
|
|
|
549
554
|
});
|
|
550
555
|
|
|
551
556
|
for (const invocation of message.invocations ?? []) {
|
|
557
|
+
const capabilities = invocation.capabilities ?? [];
|
|
558
|
+
const isProtocolInvocation =
|
|
559
|
+
capabilities.length > 0 &&
|
|
560
|
+
capabilities.every((capability) => isProtocolCapability(normalizeString(capability?.can)));
|
|
561
|
+
if (isProtocolInvocation) {
|
|
562
|
+
continue;
|
|
563
|
+
}
|
|
564
|
+
|
|
552
565
|
const proofCids = collectProofCids(invocation.proofs ?? []);
|
|
553
566
|
if (config.policy.rootDelegationCid && !proofCids.has(config.policy.rootDelegationCid)) {
|
|
554
567
|
return {
|
|
@@ -558,9 +571,12 @@ async function enforceRequestPolicy(bodyBuffer, req, config) {
|
|
|
558
571
|
};
|
|
559
572
|
}
|
|
560
573
|
|
|
561
|
-
for (const capability of
|
|
574
|
+
for (const capability of capabilities) {
|
|
562
575
|
const withValue = normalizeString(capability?.with);
|
|
563
576
|
const canValue = normalizeString(capability?.can);
|
|
577
|
+
if (isProtocolCapability(canValue)) {
|
|
578
|
+
continue;
|
|
579
|
+
}
|
|
564
580
|
if (!withValue || !canValue) {
|
|
565
581
|
return {
|
|
566
582
|
ok: false,
|
|
@@ -639,8 +655,8 @@ async function main() {
|
|
|
639
655
|
if (req.method === 'OPTIONS') {
|
|
640
656
|
res.statusCode = 204;
|
|
641
657
|
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
642
|
-
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
|
|
643
|
-
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
|
|
658
|
+
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, OPTIONS');
|
|
659
|
+
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-Amz-Checksum-Sha256');
|
|
644
660
|
res.end();
|
|
645
661
|
return;
|
|
646
662
|
}
|
|
@@ -139,7 +139,9 @@ class Handler(BaseHTTPRequestHandler):
|
|
|
139
139
|
proxy_hostname = parsed_proxy.hostname if parsed_proxy and parsed_proxy.hostname else proxy_candidate
|
|
140
140
|
service_did = str(payload.get("service_did") or "").strip()
|
|
141
141
|
service_origin = str(payload.get("service_origin") or "").strip()
|
|
142
|
+
public_storage_origin = str(payload.get("public_storage_origin") or "").strip()
|
|
142
143
|
admin_did = str(payload.get("admin_did") or "").strip()
|
|
144
|
+
admin_api_token = str(payload.get("admin_api_token") or "").strip()
|
|
143
145
|
webauthn_origin = str(payload.get("webauthn_origin") or "").strip()
|
|
144
146
|
webauthn_origin_fallbacks = str(payload.get("webauthn_origin_fallbacks") or "").strip()
|
|
145
147
|
no_start = bool(payload.get("no_start"))
|
|
@@ -169,8 +171,12 @@ class Handler(BaseHTTPRequestHandler):
|
|
|
169
171
|
args.extend(["--service-did", service_did])
|
|
170
172
|
if service_origin:
|
|
171
173
|
args.extend(["--service-origin", service_origin])
|
|
174
|
+
if public_storage_origin:
|
|
175
|
+
args.extend(["--public-storage-origin", public_storage_origin])
|
|
172
176
|
if admin_did:
|
|
173
177
|
args.extend(["--admin-did", admin_did])
|
|
178
|
+
if admin_api_token:
|
|
179
|
+
args.extend(["--admin-api-token", admin_api_token])
|
|
174
180
|
if webauthn_origin:
|
|
175
181
|
args.extend(["--webauthn-origin", webauthn_origin])
|
|
176
182
|
if webauthn_origin_fallbacks:
|