aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-cli/src/meshbay_cli/commands/trust.py
diff options
context:
space:
mode:
authorChristophe Besson <cbesson@gmail.com>2026-10-14 22:20:15 +0200
committerChristophe Besson <cbesson@gmail.com>2026-10-14 22:20:15 +0200
commit422647e7898b8df67cd11f8fb060aec23dec84aa (patch)
tree7ecca0e8df1bc8a7d26cf4b471bd44906dd9cfd1 /packages/meshbay-cli/src/meshbay_cli/commands/trust.py
parent28c88908b88c58cba4de08710f6bb268ede02d57 (diff)
downloadmeshbay-422647e7898b8df67cd11f8fb060aec23dec84aa.tar.gz
feat(cli): meshbay-cli, a command-line client for members
Signs in to the hub, lists the account's groups and their folders, and downloads and uploads files directly with each group's node, end-to-end encrypted. With no argument it opens a two-pane interactive mode (Textual). Session and device keys are kept encrypted under the system's key storage, or a 0600 key file where there is none; node keys are pinned at first contact; a new machine links as a device. Packaged on its own on Linux (deb and rpm, on meshbay-common's venv, with a man page generated from the parser) and frozen next to meshbay-node in the full Windows installer, not the light one, with a self-test the build runs. client/keys.py ports keyderive.js and is LGPL like it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'packages/meshbay-cli/src/meshbay_cli/commands/trust.py')
-rw-r--r--packages/meshbay-cli/src/meshbay_cli/commands/trust.py54
1 files changed, 54 insertions, 0 deletions
diff --git a/packages/meshbay-cli/src/meshbay_cli/commands/trust.py b/packages/meshbay-cli/src/meshbay_cli/commands/trust.py
new file mode 100644
index 0000000..f48a955
--- /dev/null
+++ b/packages/meshbay-cli/src/meshbay_cli/commands/trust.py
@@ -0,0 +1,54 @@
+"""
+`trust`: the node keys this machine pinned at first contact.
+
+A node presenting another key than the one pinned is refused outright (exit 6,
+docs/MESHBAY_DESIGN.md ยง5.2). `trust reset NODE` is the deliberate way out, for a
+rotation the node's operator has announced โ€” the counterpart of the reset in
+the application's Settings. Nothing resets a pin by itself.
+"""
+
+import json
+import sys
+
+from ..client.account import Account, no_passphrase
+from ..errors import NOT_FOUND, USAGE, CliError
+from ..output import safe, sentence
+from .context import Context
+
+HELP = "the node keys pinned at first contact: list them, or forget one"
+
+
+def add_parser(sub, parent) -> None:
+ p = sub.add_parser("trust", parents=[parent], help=HELP, description=sentence(HELP)
+ + ". A node whose key changed is refused until its pin is reset.")
+ p.add_argument("verb", choices=("list", "reset"))
+ p.add_argument("node", nargs="?", help="the node's id, as the refusal names it")
+
+
+async def run(ctx: Context) -> int:
+ a = ctx.args
+ if not ctx.store.peek():
+ pins = {}
+ account = None
+ else:
+ account = Account.open(ctx.hub, None, no_passphrase, ctx.store)
+ pins = account.state.get("pins", {}) if account.remembered else {}
+ if a.verb == "list":
+ if a.json:
+ json.dump(pins, sys.stdout, indent=1)
+ print()
+ else:
+ for node_id, pk in sorted(pins.items()):
+ print(f"{safe(node_id)} {safe(pk)}")
+ return 0
+ if not a.node:
+ raise CliError(USAGE, "Name the node: meshbay-cli trust reset NODE")
+ if a.node not in pins:
+ raise CliError(NOT_FOUND, f"No key is pinned for the node {safe(a.node)}.")
+
+ def drop(state: dict, secrets: dict) -> None:
+ state.get("pins", {}).pop(a.node, None)
+ await account.update(drop)
+ print(f"Forgot the key pinned for {safe(a.node)}; the next connection pins the key "
+ "it presents.")
+ return 0