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
sidecar/fleetmesh_ssb.py
ADDED
|
@@ -0,0 +1,603 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Secure Scuttlebutt (SSB) Sidecar Daemon.
|
|
3
|
+
|
|
4
|
+
fleetmesh-ssb DIR [--port N] [--socket PATH] [--network-key HEX]
|
|
5
|
+
|
|
6
|
+
Runs the Secure Scuttlebutt peer-to-peer transport daemon in a decoupled process:
|
|
7
|
+
- Listens on TCP (default port 8008) for inbound SSB peers.
|
|
8
|
+
- Performs 4-phase Secret Handshake (SHS) mutual authentication.
|
|
9
|
+
- Wraps connections in 34-byte Box Stream duplex encryption.
|
|
10
|
+
- Multiplexes MuxRPC procedure calls (createHistoryStream, whoami, blobs).
|
|
11
|
+
- Communicates with meshnode over local Unix domain socket (ssb.sock) using
|
|
12
|
+
newline-delimited JSON IPC protocol `fleetmesh-ssb/1`.
|
|
13
|
+
"""
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import argparse
|
|
17
|
+
import base64
|
|
18
|
+
import hashlib
|
|
19
|
+
import json
|
|
20
|
+
import logging
|
|
21
|
+
import os
|
|
22
|
+
import signal
|
|
23
|
+
import socket
|
|
24
|
+
import sys
|
|
25
|
+
import threading
|
|
26
|
+
import time
|
|
27
|
+
from typing import Any, Mapping, MutableMapping, Optional, Sequence
|
|
28
|
+
|
|
29
|
+
_ROOT = os.path.abspath(os.path.join(os.path.dirname(os.path.abspath(__file__)), '..'))
|
|
30
|
+
_CORE = os.path.abspath(os.path.join(_ROOT, '..', 'core'))
|
|
31
|
+
if _ROOT not in sys.path:
|
|
32
|
+
sys.path.insert(0, _ROOT)
|
|
33
|
+
if _CORE not in sys.path:
|
|
34
|
+
sys.path.insert(0, _CORE)
|
|
35
|
+
|
|
36
|
+
from cryptography.hazmat.primitives.asymmetric import ed25519
|
|
37
|
+
|
|
38
|
+
from fleetmesh_core.crypto.ssb import (
|
|
39
|
+
BOX_STREAM_HEADER_LENGTH,
|
|
40
|
+
SSB_MAINNET_NETWORK_KEY,
|
|
41
|
+
BoxStreamDecryptor,
|
|
42
|
+
BoxStreamEncryptor,
|
|
43
|
+
SHSClient,
|
|
44
|
+
SHSServer,
|
|
45
|
+
format_ssb_feed_id,
|
|
46
|
+
parse_ssb_feed_id,
|
|
47
|
+
)
|
|
48
|
+
from node.protocols.ssb.muxrpc import (
|
|
49
|
+
MuxRPCMessage,
|
|
50
|
+
MuxRPCRouter,
|
|
51
|
+
build_response,
|
|
52
|
+
build_stream_end,
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
log = logging.getLogger('fleetmesh-ssb')
|
|
56
|
+
|
|
57
|
+
PROTOCOL_VERSION = "fleetmesh-ssb/1"
|
|
58
|
+
DEFAULT_PORT = 8008
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class SSBSidecarClient:
|
|
62
|
+
"""IPC client used by meshnode compartments to communicate with the SSB daemon."""
|
|
63
|
+
|
|
64
|
+
def __init__(self, socket_path: str, timeout: float = 10.0):
|
|
65
|
+
self.socket_path = socket_path
|
|
66
|
+
self.timeout = timeout
|
|
67
|
+
self._sock: Optional[socket.socket] = None
|
|
68
|
+
self._lock = threading.RLock()
|
|
69
|
+
|
|
70
|
+
def connect(self) -> None:
|
|
71
|
+
with self._lock:
|
|
72
|
+
if self._sock is not None:
|
|
73
|
+
return
|
|
74
|
+
s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
|
75
|
+
s.settimeout(self.timeout)
|
|
76
|
+
s.connect(self.socket_path)
|
|
77
|
+
self._sock = s
|
|
78
|
+
|
|
79
|
+
def close(self) -> None:
|
|
80
|
+
with self._lock:
|
|
81
|
+
if self._sock:
|
|
82
|
+
try:
|
|
83
|
+
self._sock.close()
|
|
84
|
+
except Exception:
|
|
85
|
+
pass
|
|
86
|
+
self._sock = None
|
|
87
|
+
|
|
88
|
+
def send_command(self, cmd: Mapping[str, Any]) -> Mapping[str, Any]:
|
|
89
|
+
"""Send a command dictionary and receive response."""
|
|
90
|
+
with self._lock:
|
|
91
|
+
if self._sock is None:
|
|
92
|
+
self.connect()
|
|
93
|
+
|
|
94
|
+
assert self._sock is not None
|
|
95
|
+
payload = json.dumps(cmd).encode('utf-8') + b'\n'
|
|
96
|
+
self._sock.sendall(payload)
|
|
97
|
+
|
|
98
|
+
# Read response line
|
|
99
|
+
buf = bytearray()
|
|
100
|
+
while b'\n' not in buf:
|
|
101
|
+
chunk = self._sock.recv(4096)
|
|
102
|
+
if not chunk:
|
|
103
|
+
raise ConnectionResetError("SSB daemon closed IPC connection")
|
|
104
|
+
buf.extend(chunk)
|
|
105
|
+
|
|
106
|
+
line = bytes(buf.split(b'\n', 1)[0])
|
|
107
|
+
return json.loads(line.decode('utf-8'))
|
|
108
|
+
|
|
109
|
+
def status(self) -> Mapping[str, Any]:
|
|
110
|
+
return self.send_command({"cmd": "status"})
|
|
111
|
+
|
|
112
|
+
def post(self, content: Mapping[str, Any]) -> Mapping[str, Any]:
|
|
113
|
+
return self.send_command({"cmd": "post", "content": content})
|
|
114
|
+
|
|
115
|
+
def dial(self, host: str, port: int, feed_id: str) -> Mapping[str, Any]:
|
|
116
|
+
return self.send_command({"cmd": "dial", "host": host, "port": port, "feed_id": feed_id})
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
class SSBSidecarDaemon:
|
|
120
|
+
"""The Secure Scuttlebutt TCP listener, Box Stream router, and IPC daemon."""
|
|
121
|
+
|
|
122
|
+
def __init__(
|
|
123
|
+
self,
|
|
124
|
+
node_dir: str,
|
|
125
|
+
tcp_port: int = DEFAULT_PORT,
|
|
126
|
+
bind_host: str = "0.0.0.0",
|
|
127
|
+
uds_path: Optional[str] = None,
|
|
128
|
+
network_key: bytes = SSB_MAINNET_NETWORK_KEY,
|
|
129
|
+
secret_key_bytes: Optional[bytes] = None,
|
|
130
|
+
):
|
|
131
|
+
self.node_dir = node_dir
|
|
132
|
+
self.tcp_port = tcp_port
|
|
133
|
+
self.bind_host = bind_host
|
|
134
|
+
self.uds_path = uds_path or os.path.join(node_dir, "ssb.sock")
|
|
135
|
+
self.network_key = network_key
|
|
136
|
+
|
|
137
|
+
# Key setup
|
|
138
|
+
if secret_key_bytes:
|
|
139
|
+
self.priv_key = ed25519.Ed25519PrivateKey.from_private_bytes(secret_key_bytes)
|
|
140
|
+
else:
|
|
141
|
+
self.priv_key = self._load_or_create_key()
|
|
142
|
+
|
|
143
|
+
self.sk_bytes = self.priv_key.private_bytes_raw()
|
|
144
|
+
self.pk_bytes = self.priv_key.public_key().public_bytes_raw()
|
|
145
|
+
self.feed_id = format_ssb_feed_id(self.pk_bytes)
|
|
146
|
+
|
|
147
|
+
# MuxRPC Router
|
|
148
|
+
self.router = MuxRPCRouter(local_feed_id=self.feed_id)
|
|
149
|
+
self._register_rpc_methods()
|
|
150
|
+
|
|
151
|
+
# State
|
|
152
|
+
self._running = False
|
|
153
|
+
self._tcp_sock: Optional[socket.socket] = None
|
|
154
|
+
self._uds_sock: Optional[socket.socket] = None
|
|
155
|
+
self._threads: list[threading.Thread] = []
|
|
156
|
+
self._ipc_clients: list[socket.socket] = []
|
|
157
|
+
self._ipc_subscribers: set[socket.socket] = set()
|
|
158
|
+
self._peers: MutableMapping[str, Any] = {}
|
|
159
|
+
self._lock = threading.RLock()
|
|
160
|
+
|
|
161
|
+
# Local feed store (in-memory for demo / extensible to sqlite)
|
|
162
|
+
self._messages: list[Mapping[str, Any]] = []
|
|
163
|
+
|
|
164
|
+
def _load_or_create_key(self) -> ed25519.Ed25519PrivateKey:
|
|
165
|
+
"""Load ed25519 key from keys.json or generate new one."""
|
|
166
|
+
keys_file = os.path.join(self.node_dir, "keys.json")
|
|
167
|
+
if os.path.exists(keys_file):
|
|
168
|
+
try:
|
|
169
|
+
with open(keys_file, "r", encoding="utf-8") as f:
|
|
170
|
+
data = json.load(f)
|
|
171
|
+
seed_hex = data.get("transport_seed") or data.get("ed25519_seed")
|
|
172
|
+
if seed_hex:
|
|
173
|
+
seed = bytes.fromhex(seed_hex)[:32]
|
|
174
|
+
return ed25519.Ed25519PrivateKey.from_private_bytes(seed)
|
|
175
|
+
except Exception as e:
|
|
176
|
+
log.warning("failed to load key from %s: %s", keys_file, e)
|
|
177
|
+
|
|
178
|
+
return ed25519.Ed25519PrivateKey.generate()
|
|
179
|
+
|
|
180
|
+
def _register_rpc_methods(self) -> None:
|
|
181
|
+
"""Register Scuttlebutt standard MuxRPC procedures."""
|
|
182
|
+
def _handle_create_history_stream(req_id: int, args: list[Any], rpc_type: str) -> list[MuxRPCMessage]:
|
|
183
|
+
target_feed = None
|
|
184
|
+
if args and isinstance(args[0], dict):
|
|
185
|
+
target_feed = args[0].get("id")
|
|
186
|
+
|
|
187
|
+
with self._lock:
|
|
188
|
+
matching = [
|
|
189
|
+
m for m in self._messages
|
|
190
|
+
if not target_feed or m.get("author") == target_feed
|
|
191
|
+
]
|
|
192
|
+
|
|
193
|
+
out = []
|
|
194
|
+
for msg_doc in matching:
|
|
195
|
+
out.append(build_response(req_id, msg_doc, is_stream=True))
|
|
196
|
+
out.append(build_stream_end(req_id))
|
|
197
|
+
return out
|
|
198
|
+
|
|
199
|
+
self.router.register("createHistoryStream", _handle_create_history_stream)
|
|
200
|
+
|
|
201
|
+
def start(self) -> None:
|
|
202
|
+
"""Start TCP listener and IPC Unix domain socket listener."""
|
|
203
|
+
self._running = True
|
|
204
|
+
|
|
205
|
+
# 1. Bind IPC UDS socket
|
|
206
|
+
if os.path.exists(self.uds_path):
|
|
207
|
+
try:
|
|
208
|
+
os.unlink(self.uds_path)
|
|
209
|
+
except Exception:
|
|
210
|
+
pass
|
|
211
|
+
|
|
212
|
+
os.makedirs(os.path.dirname(os.path.abspath(self.uds_path)), exist_ok=True)
|
|
213
|
+
self._uds_sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
|
214
|
+
self._uds_sock.bind(self.uds_path)
|
|
215
|
+
self._uds_sock.listen(16)
|
|
216
|
+
self._uds_sock.settimeout(1.0)
|
|
217
|
+
|
|
218
|
+
# 2. Bind TCP server socket
|
|
219
|
+
self._tcp_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
220
|
+
self._tcp_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
|
221
|
+
self._tcp_sock.bind((self.bind_host, self.tcp_port))
|
|
222
|
+
# Update tcp_port if port 0 was passed
|
|
223
|
+
self.tcp_port = self._tcp_sock.getsockname()[1]
|
|
224
|
+
self._tcp_sock.listen(32)
|
|
225
|
+
self._tcp_sock.settimeout(1.0)
|
|
226
|
+
|
|
227
|
+
# Launch accept threads
|
|
228
|
+
t_ipc = threading.Thread(target=self._ipc_accept_loop, daemon=True, name="ssb-ipc-accept")
|
|
229
|
+
t_tcp = threading.Thread(target=self._tcp_accept_loop, daemon=True, name="ssb-tcp-accept")
|
|
230
|
+
t_ipc.start()
|
|
231
|
+
t_tcp.start()
|
|
232
|
+
self._threads.extend([t_ipc, t_tcp])
|
|
233
|
+
log.info("SSB sidecar started. Feed: %s, TCP: %s:%d, IPC: %s", self.feed_id, self.bind_host, self.tcp_port, self.uds_path)
|
|
234
|
+
|
|
235
|
+
def stop(self) -> None:
|
|
236
|
+
"""Stop daemon and close sockets."""
|
|
237
|
+
self._running = False
|
|
238
|
+
with self._lock:
|
|
239
|
+
if self._tcp_sock:
|
|
240
|
+
try:
|
|
241
|
+
self._tcp_sock.close()
|
|
242
|
+
except Exception:
|
|
243
|
+
pass
|
|
244
|
+
if self._uds_sock:
|
|
245
|
+
try:
|
|
246
|
+
self._uds_sock.close()
|
|
247
|
+
except Exception:
|
|
248
|
+
pass
|
|
249
|
+
if os.path.exists(self.uds_path):
|
|
250
|
+
try:
|
|
251
|
+
os.unlink(self.uds_path)
|
|
252
|
+
except Exception:
|
|
253
|
+
pass
|
|
254
|
+
|
|
255
|
+
for t in self._threads:
|
|
256
|
+
t.join(timeout=1.0)
|
|
257
|
+
|
|
258
|
+
# -------------------------------------------------------------------------
|
|
259
|
+
# IPC Loop (Unix Domain Socket)
|
|
260
|
+
# -------------------------------------------------------------------------
|
|
261
|
+
|
|
262
|
+
def _ipc_accept_loop(self) -> None:
|
|
263
|
+
while self._running:
|
|
264
|
+
try:
|
|
265
|
+
assert self._uds_sock is not None
|
|
266
|
+
conn, _ = self._uds_sock.accept()
|
|
267
|
+
t = threading.Thread(target=self._handle_ipc_client, args=(conn,), daemon=True)
|
|
268
|
+
t.start()
|
|
269
|
+
except (socket.timeout, OSError):
|
|
270
|
+
continue
|
|
271
|
+
|
|
272
|
+
def _handle_ipc_client(self, conn: socket.socket) -> None:
|
|
273
|
+
with self._lock:
|
|
274
|
+
self._ipc_clients.append(conn)
|
|
275
|
+
|
|
276
|
+
buf = bytearray()
|
|
277
|
+
try:
|
|
278
|
+
while self._running:
|
|
279
|
+
chunk = conn.recv(4096)
|
|
280
|
+
if not chunk:
|
|
281
|
+
break
|
|
282
|
+
buf.extend(chunk)
|
|
283
|
+
while b'\n' in buf:
|
|
284
|
+
line, buf = buf.split(b'\n', 1)
|
|
285
|
+
if not line.strip():
|
|
286
|
+
continue
|
|
287
|
+
try:
|
|
288
|
+
cmd = json.loads(line.decode('utf-8'))
|
|
289
|
+
resp = self._dispatch_ipc_command(conn, cmd)
|
|
290
|
+
except Exception as e:
|
|
291
|
+
resp = {"ok": False, "error": str(e)}
|
|
292
|
+
|
|
293
|
+
conn.sendall(json.dumps(resp).encode('utf-8') + b'\n')
|
|
294
|
+
except Exception:
|
|
295
|
+
pass
|
|
296
|
+
finally:
|
|
297
|
+
with self._lock:
|
|
298
|
+
if conn in self._ipc_clients:
|
|
299
|
+
self._ipc_clients.remove(conn)
|
|
300
|
+
self._ipc_subscribers.discard(conn)
|
|
301
|
+
try:
|
|
302
|
+
conn.close()
|
|
303
|
+
except Exception:
|
|
304
|
+
pass
|
|
305
|
+
|
|
306
|
+
def _dispatch_ipc_command(self, conn: socket.socket, cmd: Mapping[str, Any]) -> Mapping[str, Any]:
|
|
307
|
+
action = cmd.get("cmd")
|
|
308
|
+
if action == "status":
|
|
309
|
+
return {
|
|
310
|
+
"ok": True,
|
|
311
|
+
"protocol": PROTOCOL_VERSION,
|
|
312
|
+
"feed_id": self.feed_id,
|
|
313
|
+
"port": self.tcp_port,
|
|
314
|
+
"peers_connected": len(self._peers),
|
|
315
|
+
"messages_count": len(self._messages),
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
elif action == "subscribe":
|
|
319
|
+
with self._lock:
|
|
320
|
+
self._ipc_subscribers.add(conn)
|
|
321
|
+
return {"ok": True, "status": "subscribed"}
|
|
322
|
+
|
|
323
|
+
elif action == "post":
|
|
324
|
+
content = cmd.get("content", {})
|
|
325
|
+
msg = self.publish_post(content)
|
|
326
|
+
return {"ok": True, "message": msg}
|
|
327
|
+
|
|
328
|
+
elif action == "dial":
|
|
329
|
+
host = cmd.get("host", "127.0.0.1")
|
|
330
|
+
port = int(cmd.get("port", DEFAULT_PORT))
|
|
331
|
+
remote_feed_id = cmd.get("feed_id", "")
|
|
332
|
+
success, err = self.dial_peer(host, port, remote_feed_id)
|
|
333
|
+
if success:
|
|
334
|
+
return {"ok": True, "status": "connected"}
|
|
335
|
+
return {"ok": False, "error": err}
|
|
336
|
+
|
|
337
|
+
return {"ok": False, "error": f"unknown command '{action}'"}
|
|
338
|
+
|
|
339
|
+
# -------------------------------------------------------------------------
|
|
340
|
+
# TCP Accept Loop & Peer Connections
|
|
341
|
+
# -------------------------------------------------------------------------
|
|
342
|
+
|
|
343
|
+
def _tcp_accept_loop(self) -> None:
|
|
344
|
+
while self._running:
|
|
345
|
+
try:
|
|
346
|
+
assert self._tcp_sock is not None
|
|
347
|
+
conn, addr = self._tcp_sock.accept()
|
|
348
|
+
t = threading.Thread(target=self._handle_inbound_peer, args=(conn, addr), daemon=True)
|
|
349
|
+
t.start()
|
|
350
|
+
except (socket.timeout, OSError):
|
|
351
|
+
continue
|
|
352
|
+
|
|
353
|
+
def _handle_inbound_peer(self, sock: socket.socket, addr: tuple[str, int]) -> None:
|
|
354
|
+
"""Handle incoming TCP connection from a remote SSB peer."""
|
|
355
|
+
sock.settimeout(15.0)
|
|
356
|
+
try:
|
|
357
|
+
# 1. SHS Handshake (as Server)
|
|
358
|
+
server = SHSServer(self.sk_bytes, network_key=self.network_key)
|
|
359
|
+
|
|
360
|
+
# Phase 1: Client Hello
|
|
361
|
+
msg1 = self._recv_exact(sock, 64)
|
|
362
|
+
server.verify_client_hello(msg1)
|
|
363
|
+
|
|
364
|
+
# Phase 2: Server Hello
|
|
365
|
+
msg2 = server.create_server_hello()
|
|
366
|
+
sock.sendall(msg2)
|
|
367
|
+
|
|
368
|
+
# Phase 3: Client Authenticate
|
|
369
|
+
msg3 = self._recv_exact(sock, 112)
|
|
370
|
+
remote_pk = server.verify_client_authenticate(msg3)
|
|
371
|
+
remote_feed = format_ssb_feed_id(remote_pk)
|
|
372
|
+
|
|
373
|
+
# Phase 4: Server Accept
|
|
374
|
+
msg4 = server.create_server_accept()
|
|
375
|
+
sock.sendall(msg4)
|
|
376
|
+
|
|
377
|
+
# Box Stream session keys
|
|
378
|
+
enc_k, enc_n, dec_k, dec_n = server.get_box_stream_keys()
|
|
379
|
+
enc = BoxStreamEncryptor(enc_k, enc_n)
|
|
380
|
+
dec = BoxStreamDecryptor(dec_k, dec_n)
|
|
381
|
+
|
|
382
|
+
with self._lock:
|
|
383
|
+
self._peers[remote_feed] = {"addr": addr, "sock": sock}
|
|
384
|
+
|
|
385
|
+
log.info("Inbound SSB peer handshake authenticated: %s from %s", remote_feed, addr)
|
|
386
|
+
|
|
387
|
+
# Multiplex MuxRPC over BoxStream
|
|
388
|
+
self._muxrpc_loop(sock, enc, dec, remote_feed)
|
|
389
|
+
|
|
390
|
+
except Exception as e:
|
|
391
|
+
log.debug("SSB inbound peer session terminated: %s", e)
|
|
392
|
+
finally:
|
|
393
|
+
try:
|
|
394
|
+
sock.close()
|
|
395
|
+
except Exception:
|
|
396
|
+
pass
|
|
397
|
+
|
|
398
|
+
def dial_peer(self, host: str, port: int, remote_feed_id: str) -> tuple[bool, str]:
|
|
399
|
+
"""Dial a remote peer, execute SHS, and start MuxRPC loop."""
|
|
400
|
+
try:
|
|
401
|
+
remote_pk = parse_ssb_feed_id(remote_feed_id)
|
|
402
|
+
except Exception as e:
|
|
403
|
+
return False, f"invalid feed ID: {e}"
|
|
404
|
+
|
|
405
|
+
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
406
|
+
sock.settimeout(15.0)
|
|
407
|
+
try:
|
|
408
|
+
sock.connect((host, port))
|
|
409
|
+
client = SHSClient(self.sk_bytes, server_ed25519_pk=remote_pk, network_key=self.network_key)
|
|
410
|
+
|
|
411
|
+
# Phase 1: Client Hello
|
|
412
|
+
msg1 = client.create_client_hello()
|
|
413
|
+
sock.sendall(msg1)
|
|
414
|
+
|
|
415
|
+
# Phase 2: Server Hello
|
|
416
|
+
msg2 = self._recv_exact(sock, 64)
|
|
417
|
+
client.verify_server_hello(msg2)
|
|
418
|
+
|
|
419
|
+
# Phase 3: Client Authenticate
|
|
420
|
+
msg3 = client.create_client_authenticate()
|
|
421
|
+
sock.sendall(msg3)
|
|
422
|
+
|
|
423
|
+
# Phase 4: Server Accept
|
|
424
|
+
msg4 = self._recv_exact(sock, 80)
|
|
425
|
+
client.verify_server_accept(msg4)
|
|
426
|
+
|
|
427
|
+
# Box Stream session keys
|
|
428
|
+
enc_k, enc_n, dec_k, dec_n = client.get_box_stream_keys()
|
|
429
|
+
enc = BoxStreamEncryptor(enc_k, enc_n)
|
|
430
|
+
dec = BoxStreamDecryptor(dec_k, dec_n)
|
|
431
|
+
|
|
432
|
+
with self._lock:
|
|
433
|
+
self._peers[remote_feed_id] = {"addr": (host, port), "sock": sock}
|
|
434
|
+
|
|
435
|
+
t = threading.Thread(
|
|
436
|
+
target=self._muxrpc_loop,
|
|
437
|
+
args=(sock, enc, dec, remote_feed_id),
|
|
438
|
+
daemon=True,
|
|
439
|
+
)
|
|
440
|
+
t.start()
|
|
441
|
+
return True, ""
|
|
442
|
+
except Exception as e:
|
|
443
|
+
try:
|
|
444
|
+
sock.close()
|
|
445
|
+
except Exception:
|
|
446
|
+
pass
|
|
447
|
+
return False, str(e)
|
|
448
|
+
|
|
449
|
+
def _muxrpc_loop(
|
|
450
|
+
self,
|
|
451
|
+
sock: socket.socket,
|
|
452
|
+
enc: BoxStreamEncryptor,
|
|
453
|
+
dec: BoxStreamDecryptor,
|
|
454
|
+
remote_feed_id: str,
|
|
455
|
+
) -> None:
|
|
456
|
+
"""Read and write MuxRPC packets over decrypted Box Stream."""
|
|
457
|
+
muxrpc_buf = bytearray()
|
|
458
|
+
try:
|
|
459
|
+
while self._running:
|
|
460
|
+
# Read 34-byte Box Stream header
|
|
461
|
+
hdr_bytes = self._recv_exact(sock, BOX_STREAM_HEADER_LENGTH)
|
|
462
|
+
length, tag = dec.decrypt_header(hdr_bytes)
|
|
463
|
+
if length is None or tag is None:
|
|
464
|
+
# Stream goodbye received
|
|
465
|
+
break
|
|
466
|
+
|
|
467
|
+
body_ciphertext = self._recv_exact(sock, length)
|
|
468
|
+
payload_plaintext = dec.decrypt_body(tag, body_ciphertext)
|
|
469
|
+
muxrpc_buf.extend(payload_plaintext)
|
|
470
|
+
|
|
471
|
+
# Parse any complete MuxRPC messages
|
|
472
|
+
while muxrpc_buf:
|
|
473
|
+
rpc_msg, consumed = MuxRPCMessage.from_bytes(bytes(muxrpc_buf))
|
|
474
|
+
if rpc_msg is None:
|
|
475
|
+
break
|
|
476
|
+
muxrpc_buf = muxrpc_buf[consumed:]
|
|
477
|
+
|
|
478
|
+
# Dispatch request and send responses
|
|
479
|
+
if rpc_msg.req > 0:
|
|
480
|
+
responses = self.router.handle_request(rpc_msg)
|
|
481
|
+
for r in responses:
|
|
482
|
+
pkt = enc.encrypt_packet(r.encode())
|
|
483
|
+
sock.sendall(pkt)
|
|
484
|
+
except Exception as e:
|
|
485
|
+
log.debug("MuxRPC loop ended for %s: %s", remote_feed_id, e)
|
|
486
|
+
finally:
|
|
487
|
+
with self._lock:
|
|
488
|
+
self._peers.pop(remote_feed_id, None)
|
|
489
|
+
try:
|
|
490
|
+
sock.sendall(enc.encrypt_goodbye())
|
|
491
|
+
except Exception:
|
|
492
|
+
pass
|
|
493
|
+
try:
|
|
494
|
+
sock.close()
|
|
495
|
+
except Exception:
|
|
496
|
+
pass
|
|
497
|
+
|
|
498
|
+
def _recv_exact(self, sock: socket.socket, num_bytes: int) -> bytes:
|
|
499
|
+
buf = bytearray()
|
|
500
|
+
while len(buf) < num_bytes and self._running:
|
|
501
|
+
chunk = sock.recv(num_bytes - len(buf))
|
|
502
|
+
if not chunk:
|
|
503
|
+
raise ConnectionResetError("Socket closed prematurely")
|
|
504
|
+
buf.extend(chunk)
|
|
505
|
+
return bytes(buf)
|
|
506
|
+
|
|
507
|
+
def publish_post(self, content: Mapping[str, Any]) -> Mapping[str, Any]:
|
|
508
|
+
"""Create and sign a new post message on the local feed."""
|
|
509
|
+
with self._lock:
|
|
510
|
+
seq = len(self._messages) + 1
|
|
511
|
+
prev = self._messages[-1].get("key") if self._messages else None
|
|
512
|
+
now_ms = int(time.time() * 1000)
|
|
513
|
+
|
|
514
|
+
msg_value = {
|
|
515
|
+
"previous": prev,
|
|
516
|
+
"author": self.feed_id,
|
|
517
|
+
"sequence": seq,
|
|
518
|
+
"timestamp": now_ms,
|
|
519
|
+
"hash": "sha256",
|
|
520
|
+
"content": dict(content),
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
canonical = json.dumps(msg_value, sort_keys=True, separators=(',', ':')).encode('utf-8')
|
|
524
|
+
sig = self.priv_key.sign(canonical)
|
|
525
|
+
sig_b64 = base64.b64encode(sig).decode('ascii') + ".sig.ed25519"
|
|
526
|
+
msg_value["signature"] = sig_b64
|
|
527
|
+
|
|
528
|
+
msg_hash = hashlib.sha256(canonical).digest()
|
|
529
|
+
msg_key = f"%{base64.b64encode(msg_hash).decode('ascii')}.sha256"
|
|
530
|
+
|
|
531
|
+
record = {
|
|
532
|
+
"key": msg_key,
|
|
533
|
+
"value": msg_value,
|
|
534
|
+
"timestamp": now_ms,
|
|
535
|
+
}
|
|
536
|
+
self._messages.append(record)
|
|
537
|
+
|
|
538
|
+
# Notify IPC listeners
|
|
539
|
+
self._notify_ipc({
|
|
540
|
+
"event": "message",
|
|
541
|
+
"feed_id": self.feed_id,
|
|
542
|
+
"sequence": seq,
|
|
543
|
+
"message": record,
|
|
544
|
+
})
|
|
545
|
+
|
|
546
|
+
return record
|
|
547
|
+
|
|
548
|
+
def _notify_ipc(self, event_doc: Mapping[str, Any]) -> None:
|
|
549
|
+
payload = json.dumps(event_doc).encode('utf-8') + b'\n'
|
|
550
|
+
with self._lock:
|
|
551
|
+
dead = []
|
|
552
|
+
for client in self._ipc_subscribers:
|
|
553
|
+
try:
|
|
554
|
+
client.sendall(payload)
|
|
555
|
+
except Exception:
|
|
556
|
+
dead.append(client)
|
|
557
|
+
for d in dead:
|
|
558
|
+
self._ipc_subscribers.discard(d)
|
|
559
|
+
|
|
560
|
+
|
|
561
|
+
def main(argv: Optional[Sequence[str]] = None) -> int:
|
|
562
|
+
parser = argparse.ArgumentParser(description="Fleetmesh Secure Scuttlebutt Sidecar Daemon")
|
|
563
|
+
parser.add_argument("dir", help="Node state directory containing keys.json")
|
|
564
|
+
parser.add_argument("--port", type=int, default=DEFAULT_PORT, help=f"TCP listen port (default {DEFAULT_PORT})")
|
|
565
|
+
parser.add_argument("--host", default="0.0.0.0", help="TCP bind address (default 0.0.0.0)")
|
|
566
|
+
parser.add_argument("--socket", help="Path to Unix domain socket")
|
|
567
|
+
parser.add_argument("--network-key", help="Hex encoded 32-byte network capability key")
|
|
568
|
+
|
|
569
|
+
args = parser.parse_args(argv)
|
|
570
|
+
|
|
571
|
+
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(name)s: %(message)s")
|
|
572
|
+
|
|
573
|
+
net_key = bytes.fromhex(args.network_key) if args.network_key else SSB_MAINNET_NETWORK_KEY
|
|
574
|
+
|
|
575
|
+
daemon = SSBSidecarDaemon(
|
|
576
|
+
node_dir=args.dir,
|
|
577
|
+
tcp_port=args.port,
|
|
578
|
+
bind_host=args.host,
|
|
579
|
+
uds_path=args.socket,
|
|
580
|
+
network_key=net_key,
|
|
581
|
+
)
|
|
582
|
+
|
|
583
|
+
def _sig_handler(sig, frame):
|
|
584
|
+
log.info("Received termination signal %s, shutting down...", sig)
|
|
585
|
+
daemon.stop()
|
|
586
|
+
sys.exit(0)
|
|
587
|
+
|
|
588
|
+
signal.signal(signal.SIGINT, _sig_handler)
|
|
589
|
+
signal.signal(signal.SIGTERM, _sig_handler)
|
|
590
|
+
|
|
591
|
+
daemon.start()
|
|
592
|
+
|
|
593
|
+
try:
|
|
594
|
+
while daemon._running:
|
|
595
|
+
time.sleep(1.0)
|
|
596
|
+
except KeyboardInterrupt:
|
|
597
|
+
daemon.stop()
|
|
598
|
+
|
|
599
|
+
return 0
|
|
600
|
+
|
|
601
|
+
|
|
602
|
+
if __name__ == '__main__':
|
|
603
|
+
sys.exit(main())
|