fleetmesh-node 0.2.0__py3-none-any.whl
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.
- fleetmesh_node-0.2.0.dist-info/METADATA +57 -0
- fleetmesh_node-0.2.0.dist-info/RECORD +338 -0
- fleetmesh_node-0.2.0.dist-info/WHEEL +5 -0
- fleetmesh_node-0.2.0.dist-info/entry_points.txt +2 -0
- fleetmesh_node-0.2.0.dist-info/licenses/LICENSE +121 -0
- fleetmesh_node-0.2.0.dist-info/top_level.txt +2 -0
- node/RELEASE +1 -0
- node/__init__.py +35 -0
- node/app/__init__.py +49 -0
- node/app/commands.py +265 -0
- node/app/config.py +131 -0
- node/app/control.py +670 -0
- node/app/core.py +867 -0
- node/app/plugins.py +76 -0
- node/app/router.py +83 -0
- node/app/socket.py +431 -0
- node/app/systemd.py +36 -0
- node/app/views.py +204 -0
- node/channels/__init__.py +378 -0
- node/channels/bus.py +331 -0
- node/channels/journal.py +46 -0
- node/commands/__init__.py +140 -0
- node/commands/adapter.py +210 -0
- node/commands/base.py +278 -0
- node/commands/cluster_cmds.py +183 -0
- node/commands/compute_cmds.py +401 -0
- node/commands/core.py +474 -0
- node/commands/gossip_cmds.py +106 -0
- node/commands/nsite_cmds.py +141 -0
- node/commands/peering.py +402 -0
- node/commands/plugin_cmds.py +135 -0
- node/commands/sidecar_cmds.py +120 -0
- node/commands/sync.py +388 -0
- node/commands/transport_cmds.py +451 -0
- node/commands/video_cmds.py +99 -0
- node/compartment/__init__.py +124 -0
- node/compartment/base.py +868 -0
- node/compartment/command.py +458 -0
- node/compartment/context.py +163 -0
- node/compartment/every.py +108 -0
- node/compartment/handler.py +131 -0
- node/compartment/lifecycle.py +460 -0
- node/compartment/lint.py +388 -0
- node/compartment/need.py +158 -0
- node/compartment/problem.py +521 -0
- node/compartment/refusals.py +120 -0
- node/compartment/registry.py +790 -0
- node/compartment/schema.py +290 -0
- node/compartment/sidecar.py +499 -0
- node/compartment/stores.py +662 -0
- node/compartment/wire.py +225 -0
- node/compartment/worker.py +61 -0
- node/config/__init__.py +43 -0
- node/config/base.py +116 -0
- node/config/change.py +441 -0
- node/config/changestore.py +201 -0
- node/config/compartments.py +1144 -0
- node/config/node.py +318 -0
- node/config/parameter.py +563 -0
- node/config/protocols.py +274 -0
- node/config/records.py +171 -0
- node/examples/__init__.py +1 -0
- node/examples/demo.py +226 -0
- node/examples/follower.py +46 -0
- node/interfaces.py +235 -0
- node/keystore/__init__.py +27 -0
- node/keystore/core.py +103 -0
- node/keystore/enclave.py +688 -0
- node/meshnode.py +111 -0
- node/metrics/__init__.py +396 -0
- node/metrics/adapters.py +102 -0
- node/metrics/face.py +110 -0
- node/planes/__init__.py +21 -0
- node/planes/identity/__init__.py +20 -0
- node/planes/identity/announce/__init__.py +9 -0
- node/planes/identity/announce/compartment.py +447 -0
- node/planes/identity/announce/types.py +26 -0
- node/planes/identity/discovery/__init__.py +9 -0
- node/planes/identity/discovery/compartment.py +522 -0
- node/planes/identity/discovery/types.py +13 -0
- node/planes/identity/linked/__init__.py +1 -0
- node/planes/identity/linked/compartment.py +270 -0
- node/planes/identity/linked/types.py +25 -0
- node/planes/identity/presence/__init__.py +7 -0
- node/planes/identity/presence/compartment.py +409 -0
- node/planes/identity/presence/types.py +14 -0
- node/planes/operations/__init__.py +13 -0
- node/planes/operations/scheduler/__init__.py +9 -0
- node/planes/operations/scheduler/compartment.py +353 -0
- node/planes/operations/scheduler/types.py +21 -0
- node/planes/operations/updates/__init__.py +17 -0
- node/planes/operations/updates/core.py +310 -0
- node/planes/operations/updates/store.py +79 -0
- node/planes/operations/updates/types.py +20 -0
- node/planes/peering/__init__.py +28 -0
- node/planes/peering/budget/__init__.py +43 -0
- node/planes/peering/budget/chain.py +43 -0
- node/planes/peering/budget/core.py +537 -0
- node/planes/peering/budget/evaluation.py +80 -0
- node/planes/peering/budget/meter.py +100 -0
- node/planes/peering/budget/receipts.py +439 -0
- node/planes/peering/budget/shape.py +101 -0
- node/planes/peering/budget/tick.py +110 -0
- node/planes/peering/budget/types.py +67 -0
- node/planes/peering/budget/windows.py +346 -0
- node/planes/peering/capacity/__init__.py +7 -0
- node/planes/peering/capacity/compartment.py +342 -0
- node/planes/peering/capacity/types.py +24 -0
- node/planes/peering/peering/__init__.py +25 -0
- node/planes/peering/peering/core.py +464 -0
- node/planes/peering/peering/envelope.py +142 -0
- node/planes/peering/peering/handshake.py +198 -0
- node/planes/peering/peering/shape.py +80 -0
- node/planes/peering/peering/types.py +39 -0
- node/planes/peering/reporter/__init__.py +42 -0
- node/planes/peering/reporter/constants.py +29 -0
- node/planes/peering/reporter/core.py +299 -0
- node/planes/peering/reporter/finding.py +74 -0
- node/planes/peering/reporter/findings.py +171 -0
- node/planes/peering/reporter/publish.py +285 -0
- node/planes/peering/reporter/reading.py +193 -0
- node/planes/peering/reporter/shape.py +86 -0
- node/planes/peering/reporter/types.py +50 -0
- node/planes/peering/strain/__init__.py +9 -0
- node/planes/peering/strain/compartment.py +204 -0
- node/planes/peering/strain/types.py +21 -0
- node/planes/peering/trust/__init__.py +9 -0
- node/planes/peering/trust/compartment.py +239 -0
- node/planes/peering/trust/types.py +30 -0
- node/planes/services/__init__.py +33 -0
- node/planes/services/compute/__init__.py +160 -0
- node/planes/services/compute/common.py +134 -0
- node/planes/services/compute/compartment.py +681 -0
- node/planes/services/compute/executors/__init__.py +121 -0
- node/planes/services/compute/executors/agent.py +840 -0
- node/planes/services/compute/executors/bacalhau.py +605 -0
- node/planes/services/compute/executors/base.py +127 -0
- node/planes/services/compute/executors/harness.py +439 -0
- node/planes/services/compute/executors/mcp.py +454 -0
- node/planes/services/compute/executors/media.py +898 -0
- node/planes/services/compute/executors/nondet.py +283 -0
- node/planes/services/compute/executors/providers.py +499 -0
- node/planes/services/compute/executors/registry.py +140 -0
- node/planes/services/compute/executors/wasm.py +244 -0
- node/planes/services/compute/executors/zk.py +604 -0
- node/planes/services/compute/jobs.py +227 -0
- node/planes/services/compute/load.py +180 -0
- node/planes/services/compute/market.py +433 -0
- node/planes/services/compute/sandbox.py +282 -0
- node/planes/services/compute/types.py +84 -0
- node/planes/services/compute_wire/__init__.py +58 -0
- node/planes/services/compute_wire/compartment.py +256 -0
- node/planes/services/compute_wire/messages.py +320 -0
- node/planes/services/compute_wire/sealing.py +147 -0
- node/planes/services/compute_wire/session.py +572 -0
- node/planes/services/compute_wire/types.py +49 -0
- node/planes/services/compute_wire/verify.py +331 -0
- node/planes/services/dvm/__init__.py +24 -0
- node/planes/services/dvm/gateway.py +526 -0
- node/planes/services/dvm/protocol.py +35 -0
- node/planes/services/dvm/translate.py +15 -0
- node/planes/services/dvm/types.py +25 -0
- node/planes/services/gateway/__init__.py +40 -0
- node/planes/services/gateway/settle.py +279 -0
- node/planes/services/gateway/translate.py +644 -0
- node/planes/services/gossip/__init__.py +22 -0
- node/planes/services/gossip/core.py +929 -0
- node/planes/services/gossip/envelope.py +154 -0
- node/planes/services/gossip/mesh.py +127 -0
- node/planes/services/gossip/types.py +50 -0
- node/planes/services/nsite/__init__.py +7 -0
- node/planes/services/nsite/client.py +62 -0
- node/planes/services/nsite/compartment.py +752 -0
- node/planes/services/nsite/types.py +44 -0
- node/planes/services/paidfetch/__init__.py +7 -0
- node/planes/services/paidfetch/compartment.py +862 -0
- node/planes/services/paidfetch/types.py +99 -0
- node/planes/services/settle/__init__.py +27 -0
- node/planes/services/settle/compartment.py +384 -0
- node/planes/services/settle/engine.py +456 -0
- node/planes/services/settle/gateways.py +541 -0
- node/planes/services/settle/payment.py +512 -0
- node/planes/services/settle/store.py +74 -0
- node/planes/services/settle/types.py +58 -0
- node/planes/services/sidecars/__init__.py +8 -0
- node/planes/services/sidecars/compartment.py +461 -0
- node/planes/services/sidecars/manifest.py +304 -0
- node/planes/services/sidecars/manifests/10-grasp.json +37 -0
- node/planes/services/sidecars/manifests/20-ncontainer.json +31 -0
- node/planes/services/sidecars/manifests/30-magpie.json +28 -0
- node/planes/services/sidecars/types.py +43 -0
- node/planes/services/video/__init__.py +106 -0
- node/planes/services/video/barter.py +407 -0
- node/planes/services/video/cli.py +158 -0
- node/planes/services/video/compartment.py +932 -0
- node/planes/services/video/ingest.py +524 -0
- node/planes/services/video/ledger.py +705 -0
- node/planes/services/video/packager.py +847 -0
- node/planes/services/video/player.py +456 -0
- node/planes/services/video/timed_segments.py +124 -0
- node/planes/services/video/transit.py +478 -0
- node/planes/store/__init__.py +24 -0
- node/planes/store/blob_replication/__init__.py +7 -0
- node/planes/store/blob_replication/compartment.py +496 -0
- node/planes/store/blob_replication/types.py +35 -0
- node/planes/store/blob_wire/__init__.py +48 -0
- node/planes/store/blob_wire/client.py +57 -0
- node/planes/store/blob_wire/core.py +359 -0
- node/planes/store/blob_wire/intake.py +74 -0
- node/planes/store/blob_wire/messages.py +85 -0
- node/planes/store/projected/__init__.py +7 -0
- node/planes/store/projected/compartment.py +258 -0
- node/planes/store/projected/types.py +29 -0
- node/planes/store/retention/__init__.py +7 -0
- node/planes/store/retention/compartment.py +214 -0
- node/planes/store/retention/types.py +12 -0
- node/planes/store/sync/__init__.py +35 -0
- node/planes/store/sync/core.py +311 -0
- node/planes/store/sync/envelope.py +106 -0
- node/planes/store/sync/handover.py +206 -0
- node/planes/store/sync/initiator.py +128 -0
- node/planes/store/sync/responder.py +114 -0
- node/planes/store/sync/rules.py +179 -0
- node/planes/store/sync/shape.py +80 -0
- node/planes/store/sync/types.py +51 -0
- node/planes/transport/__init__.py +23 -0
- node/planes/transport/bridge/__init__.py +13 -0
- node/planes/transport/bridge/compartment.py +785 -0
- node/planes/transport/bridge/ratelimit.py +44 -0
- node/planes/transport/bridge/types.py +43 -0
- node/planes/transport/lan/__init__.py +9 -0
- node/planes/transport/lan/compartment.py +446 -0
- node/planes/transport/lan/types.py +25 -0
- node/planes/transport/link/__init__.py +7 -0
- node/planes/transport/link/compartment.py +257 -0
- node/planes/transport/link/types.py +27 -0
- node/planes/transport/mediation/__init__.py +45 -0
- node/planes/transport/mediation/compartment.py +238 -0
- node/planes/transport/mediation/leaf.py +107 -0
- node/planes/transport/mediation/messages.py +302 -0
- node/planes/transport/mediation/service.py +233 -0
- node/planes/transport/mediation/store.py +37 -0
- node/planes/transport/mediation/types.py +20 -0
- node/planes/transport/p2p/__init__.py +7 -0
- node/planes/transport/p2p/compartment.py +349 -0
- node/planes/transport/p2p/types.py +22 -0
- node/protocols/__init__.py +277 -0
- node/protocols/activitypub/__init__.py +56 -0
- node/protocols/activitypub/actor.py +230 -0
- node/protocols/activitypub/federation.py +490 -0
- node/protocols/activitypub/media.py +145 -0
- node/protocols/activitypub/models.py +286 -0
- node/protocols/activitypub/package.py +271 -0
- node/protocols/activitypub/store.py +564 -0
- node/protocols/arcade/__init__.py +26 -0
- node/protocols/atproto/__init__.py +13 -0
- node/protocols/atproto/firehose.py +126 -0
- node/protocols/atproto/metering.py +165 -0
- node/protocols/atproto/package.py +155 -0
- node/protocols/atproto/repo.py +250 -0
- node/protocols/atproto/xrpc.py +285 -0
- node/protocols/base.py +109 -0
- node/protocols/circles/__init__.py +29 -0
- node/protocols/dm/__init__.py +50 -0
- node/protocols/grasp/__init__.py +22 -0
- node/protocols/isotime.py +15 -0
- node/protocols/nostr/__init__.py +51 -0
- node/protocols/nostr/blobstore.py +383 -0
- node/protocols/nostr/blossom.py +627 -0
- node/protocols/nostr/client.py +377 -0
- node/protocols/nostr/descriptor.py +211 -0
- node/protocols/nostr/eventstore.py +304 -0
- node/protocols/nostr/package.py +182 -0
- node/protocols/nostr/peers.py +109 -0
- node/protocols/nostr/relayserver.py +656 -0
- node/protocols/p2panda/__init__.py +22 -0
- node/protocols/p2panda/identity.py +19 -0
- node/protocols/p2panda/package.py +236 -0
- node/protocols/p2panda/rung.py +152 -0
- node/protocols/p2panda/store.py +273 -0
- node/protocols/rhio/__init__.py +33 -0
- node/protocols/rhio/package.py +275 -0
- node/protocols/rhio/rung.py +136 -0
- node/protocols/rhio/store.py +1423 -0
- node/protocols/ssb/muxrpc.py +256 -0
- node/protocols/ssb/package.py +267 -0
- node/protocols/textile/__init__.py +111 -0
- node/protocols/textile/transducers.py +259 -0
- node/release.py +119 -0
- node/storage/__init__.py +88 -0
- node/storage/accounts.py +305 -0
- node/storage/adapter.py +161 -0
- node/storage/base.py +385 -0
- node/storage/dialect.py +209 -0
- node/storage/grants.py +317 -0
- node/storage/locator.py +150 -0
- node/storage/postgres.py +294 -0
- node/storage/projected.py +953 -0
- node/storage/sharding.py +448 -0
- node/storage/standing.py +568 -0
- node/testnet/__init__.py +9 -0
- node/testnet/cluster.py +731 -0
- node/testnet/fips_daemon.py +230 -0
- node/transport/__init__.py +94 -0
- node/transport/const.py +110 -0
- node/transport/didcomm_inbound.py +251 -0
- node/transport/dnstxt.py +169 -0
- node/transport/fips_rung/__init__.py +67 -0
- node/transport/fips_rung/core.py +666 -0
- node/transport/fips_rung/listener.py +96 -0
- node/transport/fips_rung/types.py +12 -0
- node/transport/host.py +334 -0
- node/transport/iroh_rung/__init__.py +29 -0
- node/transport/iroh_rung/core.py +477 -0
- node/transport/iroh_rung/quic.py +942 -0
- node/transport/iroh_rung/types.py +22 -0
- node/transport/ladder.py +145 -0
- node/transport/lineproto.py +49 -0
- node/transport/mdns.py +303 -0
- node/transport/netfetch.py +283 -0
- node/transport/relay_rung/__init__.py +23 -0
- node/transport/relay_rung/cooldown.py +87 -0
- node/transport/relay_rung/core.py +138 -0
- node/transport/relay_rung/inbound.py +161 -0
- node/transport/relay_rung/ledger.py +104 -0
- node/transport/relay_rung/listen.py +121 -0
- node/transport/relay_rung/outbound.py +181 -0
- node/transport/relay_rung/shape.py +71 -0
- node/transport/relay_rung/types.py +58 -0
- node/transport/relay_rung/wire.py +63 -0
- node/transport/router.py +761 -0
- node/transport/rung.py +276 -0
- node/transport/ssb_rung.py +166 -0
- node/transport/ws.py +502 -0
- sidecar/fleetmesh_announce.py +338 -0
- sidecar/fleetmesh_p2p.py +373 -0
- sidecar/fleetmesh_rhio.py +352 -0
- sidecar/fleetmesh_ssb.py +603 -0
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
"""nsite: publish this node's static site and watch the gateway.
|
|
2
|
+
|
|
3
|
+
`publish` is the only command that moves bytes: it walks a directory,
|
|
4
|
+
PUTs each file to the node's own Blossom face (signed with the node key,
|
|
5
|
+
over loopback, so the face's auth switch does not matter), then asks the
|
|
6
|
+
daemon to sign and publish the manifest from the resulting hashes. The
|
|
7
|
+
control socket never carries file bodies.
|
|
8
|
+
"""
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import argparse
|
|
12
|
+
from typing import Any, Mapping, Optional, TypedDict
|
|
13
|
+
|
|
14
|
+
from node import keystore
|
|
15
|
+
from node.config.parameter import Parameter
|
|
16
|
+
from node.planes.services.nsite.client import upload_site_blobs, walk_site_files
|
|
17
|
+
from node.planes.services.nsite.compartment import DomainBinding, NsiteGateway, NsiteState, PublishResult
|
|
18
|
+
from .adapter import CommandDispatcher
|
|
19
|
+
from .base import _ask, _live, _status_file, add_command, emit, fail
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class DomainsResponse(TypedDict, total=False):
|
|
23
|
+
domains: list[DomainBinding]
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _face_http(a: argparse.Namespace) -> str:
|
|
27
|
+
st = _live(a, 'status') or _status_file(a.dir) or {}
|
|
28
|
+
url = st.get('face') or ''
|
|
29
|
+
if not url.startswith(('ws://', 'wss://')):
|
|
30
|
+
fail('the node is not running or its face is unknown (status has no face url)')
|
|
31
|
+
return 'http' + url[2:]
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _show_status(r: NsiteState) -> None:
|
|
35
|
+
print('nsite gateway:')
|
|
36
|
+
print(' manifests cached: %d' % r.get('manifests_cached', 0))
|
|
37
|
+
print(' domains verified: %d' % r.get('domains_verified', 0))
|
|
38
|
+
print(' bytes cached: %d' % r.get('cached_bytes', 0))
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _fmt_binding(b: Optional[DomainBinding]) -> str:
|
|
42
|
+
if not b:
|
|
43
|
+
return '(none)'
|
|
44
|
+
return '%-32s %-10s %s%s' % (b.get('domain'), b.get('status'), b.get('site_key') or '-',
|
|
45
|
+
(' - ' + (b.get('why') or '')) if b.get('why') else '')
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _show_domains(r: DomainsResponse | Mapping[str, Any]) -> None:
|
|
49
|
+
rows = r.get('domains') or []
|
|
50
|
+
if not rows:
|
|
51
|
+
print('no custom-domain bindings yet')
|
|
52
|
+
return
|
|
53
|
+
for b in rows:
|
|
54
|
+
print(_fmt_binding(b))
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _show_published(r: PublishResult) -> None:
|
|
58
|
+
print('published %d files, %d bytes' % (int(r.get('files', 0)), int(r.get('bytes', 0))))
|
|
59
|
+
print(' manifest %s (kind %s)' % (r.get('manifest'), r.get('kind')))
|
|
60
|
+
print(' version %s' % r.get('aggregate'))
|
|
61
|
+
if r.get('snapshot'):
|
|
62
|
+
print(' snapshot %s' % r['snapshot'])
|
|
63
|
+
for h in r.get('hosts') or []:
|
|
64
|
+
print(' https://%s/' % h)
|
|
65
|
+
if r.get('snapshot_label'):
|
|
66
|
+
print(' (snapshot label %s)' % r['snapshot_label'])
|
|
67
|
+
for p in r.get('published_to') or []:
|
|
68
|
+
print(' relays: %s %s' % ('ok' if p.get('ok') else 'FAILED', p.get('why') or ''))
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def cmd_publish(a: argparse.Namespace) -> int:
|
|
72
|
+
if not a.path:
|
|
73
|
+
fail('publish requires --path <directory>')
|
|
74
|
+
try:
|
|
75
|
+
files = walk_site_files(a.path)
|
|
76
|
+
except ValueError as e:
|
|
77
|
+
fail(str(e))
|
|
78
|
+
if '/index.html' not in {p for p, _ in files}:
|
|
79
|
+
print('warning: no /index.html at the root; browsers asking for / get the 404 page')
|
|
80
|
+
|
|
81
|
+
ks = keystore.Keystore.load(a.dir)
|
|
82
|
+
face = _face_http(a)
|
|
83
|
+
|
|
84
|
+
def progress(rel: str, h: str, size: int) -> None:
|
|
85
|
+
if not getattr(a, 'json', False):
|
|
86
|
+
print(' %-40s %s %7d' % (rel, h[:12], size))
|
|
87
|
+
|
|
88
|
+
paths, total = upload_site_blobs(files, face, ks.node_sk, progress=progress)
|
|
89
|
+
res = _ask(
|
|
90
|
+
a.dir,
|
|
91
|
+
'nsite.publish',
|
|
92
|
+
paths=paths,
|
|
93
|
+
site=a.site,
|
|
94
|
+
snapshot=bool(a.snapshot),
|
|
95
|
+
domains=a.domain_claim or [],
|
|
96
|
+
servers=a.server or [],
|
|
97
|
+
title=a.title,
|
|
98
|
+
timeout=60,
|
|
99
|
+
)
|
|
100
|
+
res['files'] = len(paths)
|
|
101
|
+
res['bytes'] = total
|
|
102
|
+
return emit(a, res, _show_published)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
# Build declarative dispatcher from NsiteGateway.COMMANDS
|
|
106
|
+
_cmd_map = {c.key: c for c in NsiteGateway.COMMANDS}
|
|
107
|
+
dispatcher = CommandDispatcher('nsite', default_action='status')
|
|
108
|
+
dispatcher.add_command('status', _cmd_map['sites'], human=_show_status, timeout=10)
|
|
109
|
+
dispatcher.add_command('domains', _cmd_map['domains'], human=_show_domains, timeout=10)
|
|
110
|
+
dispatcher.add_command('verify', _cmd_map['verify'], human=lambda r, a: print(_fmt_binding(r.get('binding'))), timeout=30)
|
|
111
|
+
dispatcher.add_command(
|
|
112
|
+
'forget',
|
|
113
|
+
_cmd_map['forget'],
|
|
114
|
+
human=lambda r, a: print('forgot %s' % a.domain if r.get('dropped') else 'no binding for %s' % a.domain),
|
|
115
|
+
timeout=30,
|
|
116
|
+
)
|
|
117
|
+
dispatcher.add_custom(
|
|
118
|
+
'publish',
|
|
119
|
+
handler=cmd_publish,
|
|
120
|
+
parameters=(
|
|
121
|
+
Parameter(key='path', name='path', description='publish: directory to publish', type=str, default=''),
|
|
122
|
+
Parameter(key='site', name='site', description='publish: named-site id (d tag, 1-13 of [a-z0-9-]); default is the root site', type=str, default=''),
|
|
123
|
+
Parameter(key='snapshot', name='snapshot', description='publish: also publish an immutable kind-5128 snapshot', type=bool, default=False),
|
|
124
|
+
Parameter(key='domain_claim', name='domain_claim', description='publish: claim a custom domain in the manifest (repeatable)', type=list, default=list, cli_name='--domain-claim'),
|
|
125
|
+
Parameter(key='server', name='server', description='publish: extra Blossom server URL the blobs are mirrored on (repeatable)', type=list, default=list, cli_name='--server'),
|
|
126
|
+
Parameter(key='title', name='title', description='publish: site title tag', type=str, default=''),
|
|
127
|
+
),
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def cmd_nsite(a: argparse.Namespace) -> int:
|
|
132
|
+
return dispatcher.dispatch(a)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
class NsiteCommandProvider:
|
|
136
|
+
"""Registers the nsite (NIP-5A static site) commands."""
|
|
137
|
+
|
|
138
|
+
@classmethod
|
|
139
|
+
def register(cls, sub: argparse._SubParsersAction) -> None:
|
|
140
|
+
p = add_command(sub, 'nsite', cmd_nsite, 'host static sites (NIP-5A): publish this node\'s site, manage custom domains', js=True)
|
|
141
|
+
dispatcher.register_arguments(p, action_choices=('status', 'domains', 'verify', 'forget', 'publish'))
|
node/commands/peering.py
ADDED
|
@@ -0,0 +1,402 @@
|
|
|
1
|
+
"""Peering management, bilateral grants, accounting windows, and trust tiers."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import argparse
|
|
5
|
+
import json
|
|
6
|
+
import os
|
|
7
|
+
import time
|
|
8
|
+
from typing import Any, Mapping, cast
|
|
9
|
+
|
|
10
|
+
from node import app as app_mod
|
|
11
|
+
from node.app import socket as control
|
|
12
|
+
from node.storage import accounts as accounts_mod, grants as grants_mod
|
|
13
|
+
from .base import (
|
|
14
|
+
TIERS,
|
|
15
|
+
_ask,
|
|
16
|
+
_grants,
|
|
17
|
+
_live,
|
|
18
|
+
_peer,
|
|
19
|
+
_status_file,
|
|
20
|
+
_table,
|
|
21
|
+
_when,
|
|
22
|
+
add_command,
|
|
23
|
+
emit,
|
|
24
|
+
fail,
|
|
25
|
+
load_json_arg,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _peers_table(view: list) -> None:
|
|
30
|
+
if not view:
|
|
31
|
+
print('no peers: nothing granted and nothing held')
|
|
32
|
+
return
|
|
33
|
+
rows = []
|
|
34
|
+
for v in view:
|
|
35
|
+
last = v.get('last_evaluated') or {}
|
|
36
|
+
standing = v.get('standing') or {}
|
|
37
|
+
rows.append((v['peer'][:16], (v.get('issued') or {}).get('tier') or '-', (v.get('received') or {}).get('tier') or '-',
|
|
38
|
+
('%s from %s' % (v['pending']['tier'], v['pending']['from_epoch'])) if v.get('pending') else '-',
|
|
39
|
+
('%s %s' % (last.get('epoch'), last.get('severity'))) if last else '-',
|
|
40
|
+
('%d clean / %d' % (standing['clean_windows'], standing['evaluated_windows'])) if standing else '-',
|
|
41
|
+
','.join(v.get('pulls') or []) or '-',
|
|
42
|
+
('%d' % len(v['shared'])) if v.get('shared') else '-',
|
|
43
|
+
('other %s' % v['digest'][:8]) if v.get('digest') else 'same' if v.get('descriptor') else '-'))
|
|
44
|
+
_table(('peer', 'issued', 'received', 'pending', 'last window', 'streak', 'pulling', 'shared', 'schema'), rows)
|
|
45
|
+
for v in view:
|
|
46
|
+
for n in v.get('notices') or []:
|
|
47
|
+
print('%s: %s' % (v['peer'][:16], n))
|
|
48
|
+
if v.get('not_shared'):
|
|
49
|
+
print('%s: not shared with this node: %s' % (v['peer'][:16], ', '.join(v['not_shared'])))
|
|
50
|
+
zeroed = [v for v in view if v.get('zeroed_until')]
|
|
51
|
+
for v in zeroed:
|
|
52
|
+
print('%s is zeroed after S3 until %s UTC' % (v['peer'][:16], time.strftime('%Y-%m-%d %H:%M', time.gmtime(v['zeroed_until']))))
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def cmd_peers(a: argparse.Namespace) -> int:
|
|
56
|
+
view = _live(a, 'peers', timeout=15)
|
|
57
|
+
if view is None:
|
|
58
|
+
# From disk: the grants and what the last status said.
|
|
59
|
+
g = _grants(a.dir)
|
|
60
|
+
st: Mapping[str, Any] = {}
|
|
61
|
+
try:
|
|
62
|
+
st = dict(_status_file(a.dir))
|
|
63
|
+
except SystemExit:
|
|
64
|
+
pass
|
|
65
|
+
cfg_peers: dict[str, list[str]] = {}
|
|
66
|
+
try:
|
|
67
|
+
for p in app_mod.load_config(a.dir).get('peers', []):
|
|
68
|
+
cfg_peers.setdefault(p['pubkey'], []).append(p['shard'])
|
|
69
|
+
except (OSError, ValueError):
|
|
70
|
+
pass
|
|
71
|
+
view = []
|
|
72
|
+
for pk in sorted({r['peer'] for r in g.list()}):
|
|
73
|
+
issued, received, pend = g.get(pk, grants_mod.ISSUED), g.get(pk, grants_mod.RECEIVED), g.pending(pk)
|
|
74
|
+
ev = [e for e in st.get('evaluations', []) if pk.startswith(e['peer'])]
|
|
75
|
+
view.append({'peer': pk,
|
|
76
|
+
'issued': {'tier': issued['payload'].get('tier'), 'status': issued['status']} if issued else None,
|
|
77
|
+
'received': {'tier': received['payload'].get('tier'), 'status': received['status']} if received else None,
|
|
78
|
+
'pending': {'tier': pend['payload'].get('tier'), 'from_epoch': pend['decided_epoch'] + 1} if pend else None,
|
|
79
|
+
'last_evaluated': ev[-1] if ev else None, 'standing': None,
|
|
80
|
+
'zeroed_until': None, 'pulls': cfg_peers.get(pk, []), 'descriptor': None,
|
|
81
|
+
'shared': [], 'not_shared': [], 'digest': None, 'notices': []})
|
|
82
|
+
return emit(a, view, _peers_table)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def cmd_grant(a: argparse.Namespace) -> int:
|
|
86
|
+
pk = _peer(a.dir, a.peer)
|
|
87
|
+
out = app_mod.grant_view(_grants(a.dir), pk)
|
|
88
|
+
if a.json:
|
|
89
|
+
return emit(a, out)
|
|
90
|
+
if out['issued'] is None and out['received'] is None:
|
|
91
|
+
fail('no grant either way with %s' % pk[:16])
|
|
92
|
+
out_dict = cast(Mapping[str, Any], out)
|
|
93
|
+
for direction in (grants_mod.ISSUED, grants_mod.RECEIVED):
|
|
94
|
+
row = out_dict.get(direction)
|
|
95
|
+
print('%s %s' % (direction, '(none)' if row is None else ''))
|
|
96
|
+
if row is None:
|
|
97
|
+
continue
|
|
98
|
+
p = row['payload']
|
|
99
|
+
print(' envelope %s tier %s status %s window %ss receipt every %ss'
|
|
100
|
+
% (row['envelope_id'][:16] + '…', p.get('tier'), row['status'], p.get('window'), (p.get('receipt') or ['?'])[0]))
|
|
101
|
+
limits = p.get('limits') or {}
|
|
102
|
+
print(' limits ' + ' '.join('%s %s' % (k, v) for k, v in sorted(limits.items())))
|
|
103
|
+
scope = p.get('scope') or {}
|
|
104
|
+
print(' scope pull %s push %s' % (json.dumps(scope.get('pull')), json.dumps(scope.get('push'))))
|
|
105
|
+
hist = out_dict.get(direction + '_history', [])
|
|
106
|
+
if len(hist) > 1:
|
|
107
|
+
print(' history ' + '; '.join('%s… %s from %s' % (h['envelope_id'][:8], h['tier'], h['effective_epoch']) for h in hist))
|
|
108
|
+
if out['pending']:
|
|
109
|
+
p = out['pending']
|
|
110
|
+
print('waiting tier %s from window %s (%s UTC): %s' % (p['payload'].get('tier'), p['decided_epoch'] + 1,
|
|
111
|
+
_when(p['decided_epoch'] + 1), p['note']))
|
|
112
|
+
if out['seed_applied']:
|
|
113
|
+
print('seed %s, decided at %s' % (out['seed_applied'][0], time.strftime('%Y-%m-%d %H:%M UTC', time.gmtime(out['seed_applied'][1]))
|
|
114
|
+
if out['seed_applied'][1] else 'time zero'))
|
|
115
|
+
return 0
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def cmd_windows(a: argparse.Namespace) -> int:
|
|
119
|
+
pk = _peer(a.dir, a.peer)
|
|
120
|
+
path = os.path.join(a.dir, 'accounts.sqlite')
|
|
121
|
+
if not os.path.exists(path):
|
|
122
|
+
fail('no accounts.sqlite yet: nothing has been metered')
|
|
123
|
+
# Through the store, not a bare sqlite3 connection: `windows_view` already
|
|
124
|
+
# prefers `_all` when it is given one, and a store reaches whatever backend
|
|
125
|
+
# is configured. readonly=True keeps exactly what the direct connection
|
|
126
|
+
# gave - no writes, no schema, no WAL writer lock beside a running node.
|
|
127
|
+
acc = accounts_mod.AccountStore(path, readonly=True)
|
|
128
|
+
try:
|
|
129
|
+
out = app_mod.windows_view(acc, pk, a.last)
|
|
130
|
+
finally:
|
|
131
|
+
acc.close()
|
|
132
|
+
rows = []
|
|
133
|
+
for rec in out:
|
|
134
|
+
m, cl = rec['measured'], rec['claimed'] or {}
|
|
135
|
+
rows.append((rec['epoch'], _when(rec['epoch'] + 1), rec['severity'] or 'not evaluated',
|
|
136
|
+
'%s/%s' % (m.get('req', 0), cl.get('req', '-')), '%s/%s' % (m.get('events_in', 0), cl.get('events_in', '-')),
|
|
137
|
+
'%s/%s' % (m.get('bytes_in', 0), cl.get('bytes_in', '-')), rec['receipts_in'], rec['my_receipts_sent']))
|
|
138
|
+
if a.json:
|
|
139
|
+
return emit(a, out)
|
|
140
|
+
if not rows:
|
|
141
|
+
fail('no windows metered with %s' % pk[:16])
|
|
142
|
+
print('windows with %s (measured/claimed, in the issuer frame)' % pk[:16])
|
|
143
|
+
_table(('window', 'closed UTC', 'severity', 'req', 'events_in', 'bytes_in', 'receipts in', 'sent'), rows)
|
|
144
|
+
return 0
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def cmd_peer(a: argparse.Namespace) -> int:
|
|
148
|
+
cfg = dict(app_mod.load_config(a.dir))
|
|
149
|
+
pk = _peer(a.dir, a.peer)
|
|
150
|
+
filt = load_json_arg(a.filter, '--filter')
|
|
151
|
+
if not isinstance(filt, dict):
|
|
152
|
+
fail('--filter must be a nostr filter object')
|
|
153
|
+
if a.every < 1:
|
|
154
|
+
fail('--every must be a positive number of seconds')
|
|
155
|
+
entry = {'pubkey': pk, 'shard': a.shard, 'filter': filt, 'pull_every': a.every}
|
|
156
|
+
others = [p for p in cfg.get('peers', []) if (p['pubkey'], p['shard']) != (pk, a.shard)]
|
|
157
|
+
if entry in cfg.get('peers', []):
|
|
158
|
+
# Already exactly this. Saying "will peer" here made every deploy pass
|
|
159
|
+
# restart every node, which on a public mesh is a round of farewells,
|
|
160
|
+
# replays and conn_rate refusals for nothing.
|
|
161
|
+
return emit(a, {'action': 'peer', 'peer': pk, 'shard': a.shard, 'every': a.every,
|
|
162
|
+
'peers_configured': len(cfg['peers']), 'changed': False},
|
|
163
|
+
lambda _: print('already peered with %s for shard %s; %d peers configured'
|
|
164
|
+
% (pk[:16], a.shard, len(cfg['peers']))))
|
|
165
|
+
cfg['peers'] = others + [entry]
|
|
166
|
+
app_mod.save_config(a.dir, cfg)
|
|
167
|
+
return emit(a, {'action': 'peer', 'peer': pk, 'shard': a.shard, 'every': a.every, 'peers_configured': len(cfg['peers']), 'changed': True},
|
|
168
|
+
lambda _: print('will peer with %s for shard %s on the next %s; %d peers configured'
|
|
169
|
+
% (pk[:16], a.shard, 'tick' if control.running(a.dir) else 'run', len(cfg['peers']))))
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def cmd_unpeer(a: argparse.Namespace) -> int:
|
|
173
|
+
cfg = dict(app_mod.load_config(a.dir))
|
|
174
|
+
pk = _peer(a.dir, a.peer)
|
|
175
|
+
before = len(cfg.get('peers', []))
|
|
176
|
+
cfg['peers'] = [p for p in cfg.get('peers', []) if p['pubkey'] != pk or (a.shard and p['shard'] != a.shard)]
|
|
177
|
+
app_mod.save_config(a.dir, cfg)
|
|
178
|
+
n = before - len(cfg['peers'])
|
|
179
|
+
if not n:
|
|
180
|
+
fail('%s was not being pulled from' % pk[:16])
|
|
181
|
+
return emit(a, {'action': 'unpeer', 'peer': pk, 'shard': a.shard, 'shards_removed': n, 'peers_configured': len(cfg['peers'])},
|
|
182
|
+
lambda _: print('stopped pulling %d shard(s) from %s; the grants stay live (`terminate` ends the peering)' % (n, pk[:16])))
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def _trust_table(st: dict) -> None:
|
|
186
|
+
decided = st['decided']
|
|
187
|
+
if not decided:
|
|
188
|
+
print('no operator trust decisions recorded')
|
|
189
|
+
return
|
|
190
|
+
print('%-16s %-12s %-12s %-12s %s' % ('peer', 'decided', 'held', 'state', 'when'))
|
|
191
|
+
for pk, d in sorted(decided.items()):
|
|
192
|
+
when = time.strftime('%Y-%m-%d %H:%M', time.gmtime(d['at'])) if d.get('at') else 'initial'
|
|
193
|
+
print('%-16s %-12s %-12s %-12s %s' % (pk[:16], d.get('tier', '-'), str(d.get('held') or '-'), d.get('state', '-'), when))
|
|
194
|
+
print('\nin force: %d pending: %d overridden: %d waiting: %d' % (
|
|
195
|
+
st.get('in_force', 0), len(st.get('pending') or []),
|
|
196
|
+
len(st.get('overridden') or []), len(st.get('waiting') or [])))
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def cmd_trust(a: argparse.Namespace) -> int:
|
|
200
|
+
"""The one decision that raises a tier, and it is a person's."""
|
|
201
|
+
if not getattr(a, 'tier', None):
|
|
202
|
+
st = _live(a, 'trust')
|
|
203
|
+
if st is None:
|
|
204
|
+
cfg = app_mod.load_config(a.dir)
|
|
205
|
+
trust_cfg = dict(cfg.get('trust') or {})
|
|
206
|
+
st = {
|
|
207
|
+
'decided': {
|
|
208
|
+
pk[:16]: {
|
|
209
|
+
'tier': e.get('tier') if isinstance(e, dict) else e,
|
|
210
|
+
'at': e.get('at', 0) if isinstance(e, dict) else 0,
|
|
211
|
+
'held': None,
|
|
212
|
+
'state': 'offline',
|
|
213
|
+
}
|
|
214
|
+
for pk, e in trust_cfg.items()
|
|
215
|
+
},
|
|
216
|
+
'in_force': 0,
|
|
217
|
+
'pending': [],
|
|
218
|
+
'overridden': [],
|
|
219
|
+
'waiting': [pk[:16] for pk in trust_cfg],
|
|
220
|
+
}
|
|
221
|
+
decided = st.get('decided') or {}
|
|
222
|
+
if getattr(a, 'peer', None):
|
|
223
|
+
pk_filter = _peer(a.dir, a.peer)
|
|
224
|
+
decided = {k: v for k, v in decided.items() if k == pk_filter[:16] or k == pk_filter}
|
|
225
|
+
out = dict(st)
|
|
226
|
+
out['decided'] = decided
|
|
227
|
+
return emit(a, out, _trust_table)
|
|
228
|
+
|
|
229
|
+
if not getattr(a, 'peer', None):
|
|
230
|
+
fail('meshnode trust: error: setting a tier requires a peer')
|
|
231
|
+
|
|
232
|
+
cfg = dict(app_mod.load_config(a.dir))
|
|
233
|
+
pk = _peer(a.dir, a.peer)
|
|
234
|
+
trust = dict(cfg.get('trust') or {})
|
|
235
|
+
trust[pk] = {'tier': a.tier, 'at': int(time.time())}
|
|
236
|
+
cfg['trust'] = trust
|
|
237
|
+
app_mod.save_config(a.dir, cfg)
|
|
238
|
+
|
|
239
|
+
def seeded(_: dict) -> None:
|
|
240
|
+
print('%s is %s' % (pk[:16], a.tier))
|
|
241
|
+
if _grants(a.dir).get(pk, 'issued') is None:
|
|
242
|
+
print(' no grant issued to it yet; it will open at %s when you peer' % a.tier)
|
|
243
|
+
else:
|
|
244
|
+
print(' the live grant will be re-issued at %s at the next window boundary' % a.tier)
|
|
245
|
+
return emit(a, {'peer': pk, 'tier': a.tier, 'at': trust[pk]['at']}, seeded)
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
def cmd_terminate(a: argparse.Namespace) -> int:
|
|
249
|
+
pk = _peer(a.dir, a.peer)
|
|
250
|
+
r = _ask(a.dir, 'terminate', peer=pk, reason=a.reason)
|
|
251
|
+
|
|
252
|
+
def human(r: dict) -> None:
|
|
253
|
+
told = r.get('told') or {}
|
|
254
|
+
print('terminated the peering with %s (%s); both grants are zero here' % (pk[:16], r.get('reason')))
|
|
255
|
+
if 'error' in told:
|
|
256
|
+
print(' the peer could not be told: %s' % told['error'])
|
|
257
|
+
else:
|
|
258
|
+
print(' told via %s' % ', '.join(u for u, v in told.items() if v[0]) or 'no relay accepted the message')
|
|
259
|
+
if r.get('pulls_removed'):
|
|
260
|
+
print(' %d scheduled pull(s) removed from the config' % r['pulls_removed'])
|
|
261
|
+
return emit(a, r, human)
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def _retired(r: dict) -> None:
|
|
265
|
+
print('retiring: %s' % r.get('reason'))
|
|
266
|
+
for d in r.get('did') or []:
|
|
267
|
+
print(' %s' % ' '.join(str(x) for x in d))
|
|
268
|
+
print(' %d handover(s) to %d peer(s); %d claim(s) withdrawn'
|
|
269
|
+
% (r.get('handed', 0), len(r.get('peers') or []), r.get('withdrawn', 0)))
|
|
270
|
+
if r.get('revoked'):
|
|
271
|
+
print(' the node key is revoked: a successor inherits nothing and starts on probation')
|
|
272
|
+
else:
|
|
273
|
+
print(' the key is NOT revoked; this node can be started again')
|
|
274
|
+
print(' the node is stopping, and says farewell to every peer on the way out')
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
def cmd_retire(a: argparse.Namespace) -> int:
|
|
278
|
+
"""Leave the mesh in order. Not what a restart does."""
|
|
279
|
+
r = _ask(a.dir, 'retire', reason=a.reason, revoke=bool(a.revoke), lapses_in=a.lapses_in)
|
|
280
|
+
return emit(a, r, _retired)
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
def cmd_withdraw(a: argparse.Namespace) -> int:
|
|
284
|
+
pk = _peer(a.dir, a.peer)
|
|
285
|
+
r = _ask(a.dir, 'withdraw', peer=pk, window=a.window, why=a.why)
|
|
286
|
+
|
|
287
|
+
def human(r: dict | list) -> None:
|
|
288
|
+
for row in (r if isinstance(r, list) else [r]):
|
|
289
|
+
if row['was_published']:
|
|
290
|
+
print('withdrew the report about %s for window %s (%s)' % (pk[:16], row['window'], row['report'][:16]))
|
|
291
|
+
print(' the kind 30802 is not deleted; it carries its own expiration and lapses on it')
|
|
292
|
+
else:
|
|
293
|
+
print('withdrew the finding about %s for window %s; it had not been published'
|
|
294
|
+
% (pk[:16], row['window']))
|
|
295
|
+
print(' the subject was told: %s' % row['why'])
|
|
296
|
+
return emit(a, r, human)
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
def cmd_standing(a: argparse.Namespace) -> int:
|
|
300
|
+
pk = _peer(a.dir, a.peer)
|
|
301
|
+
target = _peer(a.dir, a.target)
|
|
302
|
+
r = _ask(a.dir, 'standing', peer=pk, target=target)
|
|
303
|
+
|
|
304
|
+
def human(r: dict) -> None:
|
|
305
|
+
if r['answer'] == 'decline':
|
|
306
|
+
print('%s declined to say: %s' % (pk[:16], (r['body'] or {}).get('reason')))
|
|
307
|
+
return
|
|
308
|
+
b = r['body']
|
|
309
|
+
print('%s says of %s: tier %s, %s clean window(s) in a row, %s evaluated, %s'
|
|
310
|
+
% (pk[:16], target[:16], b.get('tier'), b.get('clean_windows'), b.get('evaluated_windows'),
|
|
311
|
+
'has adverse reports' if b.get('has_adverse_reports') else 'no adverse reports'))
|
|
312
|
+
return emit(a, r, human)
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
def cmd_reports(a: argparse.Namespace) -> int:
|
|
316
|
+
target = _peer(a.dir, a.target)
|
|
317
|
+
r = _ask(a.dir, 'reports', target=target, timeout=60)
|
|
318
|
+
|
|
319
|
+
def human(r: dict) -> None:
|
|
320
|
+
reps = r.get('reports') or []
|
|
321
|
+
if not reps:
|
|
322
|
+
print('no kind 30802 about %s on this node\'s relays' % target[:16])
|
|
323
|
+
return
|
|
324
|
+
_table(('reporter', 'severity', 'admissible', 'weight here', 'published UTC'),
|
|
325
|
+
[(x['reporter'][:16], x['severity'], 'yes' if x['admissible'] else 'no: ' + '; '.join(x['reasons'])[:60],
|
|
326
|
+
x['weight'], time.strftime('%Y-%m-%d %H:%M', time.gmtime(x['created_at']))) for x in reps])
|
|
327
|
+
print('\nthis node\'s view of %s, its own S0 weighed against them: %s' % (target[:16], r.get('view')))
|
|
328
|
+
return emit(a, r, human)
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
from ..config.parameter import Parameter
|
|
332
|
+
from .adapter import add_parameter_argument
|
|
333
|
+
|
|
334
|
+
PARAM_WIN_LAST = Parameter(key='last', name='last', description='how many, newest last (0 for all)', type=int, default=24)
|
|
335
|
+
|
|
336
|
+
PARAM_PEER_SHARD = Parameter(key='shard', name='shard', description='shard name', type=str, default='', required=True)
|
|
337
|
+
PARAM_PEER_FILTER = Parameter(key='filter', name='filter', description='a nostr filter, JSON or @file', type=str, default='', required=True)
|
|
338
|
+
PARAM_PEER_EVERY = Parameter(
|
|
339
|
+
key='every', name='every',
|
|
340
|
+
description='seconds between pulls. The default is the probation connection ceiling: four an hour. It was 600, which is six, so a peering that used the shipped default overran the grant it was shipped with',
|
|
341
|
+
type=int, default=900,
|
|
342
|
+
)
|
|
343
|
+
|
|
344
|
+
PARAM_UNPEER_SHARD = Parameter(key='shard', name='shard', description='only this shard', type=str, default='')
|
|
345
|
+
|
|
346
|
+
PARAM_TRUST_PEER = Parameter(key='peer', name='peer', description='the peer to seed a tier for (pubkey, did, npub or prefix)', type=str, default='', positional=True, required=False, nargs='?')
|
|
347
|
+
PARAM_TRUST_TIER = Parameter(key='tier', name='tier', description='the tier to seed for this peer', type=str, default=None, choices=TIERS)
|
|
348
|
+
|
|
349
|
+
PARAM_TERM_REASON = Parameter(key='reason', name='reason', description='reason for termination', type=str, default='operator')
|
|
350
|
+
|
|
351
|
+
PARAM_RET_REASON = Parameter(key='reason', name='reason', description='reason for retirement', type=str, default='operator')
|
|
352
|
+
PARAM_RET_REVOKE = Parameter(key='revoke', name='revoke', description='publish a kind 11802 as well: the key is retired for good and a successor inherits nothing', type=bool, default=False)
|
|
353
|
+
PARAM_RET_LAPSES = Parameter(key='lapses_in', name='lapses_in', cli_name='--lapses-in', description='how long the withdrawn claims stay on the relays before they expire', type=int, default=86400)
|
|
354
|
+
|
|
355
|
+
PARAM_WD_WINDOW = Parameter(key='window', name='window', description='the window epoch the finding is about', type=int, default=0, positional=True, required=True)
|
|
356
|
+
PARAM_WD_WHY = Parameter(key='why', name='why', description='what to tell the subject; recorded on the complaint thread', type=str, default=None)
|
|
357
|
+
|
|
358
|
+
PARAM_ST_TARGET = Parameter(key='target', name='target', description='the node asked about: pubkey, did:nostr or prefix', type=str, default='', positional=True, required=True)
|
|
359
|
+
|
|
360
|
+
PARAM_RP_TARGET = Parameter(key='target', name='target', description='pubkey, did:nostr or prefix', type=str, default='', positional=True, required=True)
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
class PeeringCommandProvider:
|
|
364
|
+
"""Registers peer management, trust tier, bilateral grant, and report commands."""
|
|
365
|
+
|
|
366
|
+
@classmethod
|
|
367
|
+
def register(cls, sub: argparse._SubParsersAction) -> None:
|
|
368
|
+
add_command(sub, 'peers', cmd_peers, 'every peer: tiers, pending grant, last window, standing', js=True)
|
|
369
|
+
add_command(sub, 'grant', cmd_grant, 'both grants with a peer, their history, and what waits for the boundary', peer=True, js=True)
|
|
370
|
+
|
|
371
|
+
w = add_command(sub, 'windows', cmd_windows, 'the windows this node priced for a peer', peer=True, js=True)
|
|
372
|
+
add_parameter_argument(w, PARAM_WIN_LAST)
|
|
373
|
+
|
|
374
|
+
p = add_command(sub, 'peer', cmd_peer, 'add a peer and shard to pull from', peer=True, js=True)
|
|
375
|
+
add_parameter_argument(p, PARAM_PEER_SHARD, require_flag=True)
|
|
376
|
+
add_parameter_argument(p, PARAM_PEER_FILTER, require_flag=True)
|
|
377
|
+
add_parameter_argument(p, PARAM_PEER_EVERY)
|
|
378
|
+
|
|
379
|
+
u = add_command(sub, 'unpeer', cmd_unpeer, 'stop pulling from a peer; the grants stay live', peer=True, js=True)
|
|
380
|
+
add_parameter_argument(u, PARAM_UNPEER_SHARD)
|
|
381
|
+
|
|
382
|
+
t = add_command(sub, 'trust', cmd_trust, "inspect or set a peer's tier, which nothing else can raise", js=True)
|
|
383
|
+
add_parameter_argument(t, PARAM_TRUST_PEER)
|
|
384
|
+
add_parameter_argument(t, PARAM_TRUST_TIER)
|
|
385
|
+
|
|
386
|
+
te = add_command(sub, 'terminate', cmd_terminate, 'end a peering: zero both grants and tell the peer (needs the node running)', peer=True, js=True)
|
|
387
|
+
add_parameter_argument(te, PARAM_TERM_REASON)
|
|
388
|
+
|
|
389
|
+
rt = add_command(sub, 'retire', cmd_retire, 'leave the mesh in order: hand over every asserted shard, withdraw the claims, stop', js=True)
|
|
390
|
+
add_parameter_argument(rt, PARAM_RET_REASON)
|
|
391
|
+
add_parameter_argument(rt, PARAM_RET_REVOKE)
|
|
392
|
+
add_parameter_argument(rt, PARAM_RET_LAPSES)
|
|
393
|
+
|
|
394
|
+
wd = add_command(sub, 'withdraw', cmd_withdraw, 'withdraw a finding this node made about one window (needs the node running)', peer=True, js=True)
|
|
395
|
+
add_parameter_argument(wd, PARAM_WD_WINDOW)
|
|
396
|
+
add_parameter_argument(wd, PARAM_WD_WHY)
|
|
397
|
+
|
|
398
|
+
st = add_command(sub, 'standing', cmd_standing, 'ask a peer what it knows about a third node (needs the node running)', peer=True, js=True)
|
|
399
|
+
add_parameter_argument(st, PARAM_ST_TARGET)
|
|
400
|
+
|
|
401
|
+
r = add_command(sub, 'reports', cmd_reports, 'the kind 30802s about a node, weighed by this one (needs the node running)', js=True)
|
|
402
|
+
add_parameter_argument(r, PARAM_RP_TARGET)
|