aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-node/tests
Commit message (Collapse)AuthorAgeFilesLines
* fix(packaging): setup ends a command-line client run from its installChristophe Besson20 hours1-1/+49
| | | | | | | | | | | | | | | A `meshbay-cli chat --follow` left open holds node-runtime's DLLs, so the previous version's uninstaller failed ("Failed to uninstall old application files"), a silent update included, and left the node it had stopped not running. stop-node.ps1 now ends a meshbay-cli.exe run from this install (-InstallDir, setup's $INSTDIR), and no other copy. Found through CIM, not Get-Process: setup runs the 32-bit PowerShell, which reads no path for a 64-bit process, and the first version of the stop matched nothing. The test runs the script in that PowerShell. Checked live: an update over a running follower now ends it, replaces every file and restarts the node. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* build: audit the shipped Python dependencies with pip-auditChristophe Besson23 hours1-0/+64
| | | | | | | | | | | | | | | | | | | | | No lock file pins the dependencies, so each build installs the newest releases and what ships is only known once it has. packaging/audit_deps.py runs pip-audit then: build-common.sh on the venv before stripping it, build-node-runtime.ps1 on the environment before PyInstaller freezes it. A known vulnerability stops the build, unless its id is listed in packaging/pip-audit-ignore.txt with why it does not apply. No network only skips the audit with a warning, as does a pip-audit that cannot be installed or writes no report. pip-audit runs from a venv of its own, never from the one that ships. Our packages are not audited (not on PyPI), nor pip, setuptools and wheel, which do not ship. Checked with the real build-common.sh (79 packages, none known), with PyJWT 2.14 (the build stops on its two advisories, and goes on once they are listed) and offline (a warning, the build goes on). The PowerShell step was not run here. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* test(node): the event-loop disk guard watches os.stat againChristophe Besson23 hours1-5/+20
| | | | | | | | | | | | | | | | | test_a_scan_asks_the_disk_nothing_from_the_event_loop patched Path.stat, and since the indexer learned Windows' long paths it calls os.stat(long_path(p)) and os.scandir instead: the guard saw no call at all, and asserting that the calls ran on the indexer's thread failed on an empty set. It watches os.stat and os.scandir now (Path.stat goes through os.stat too), counting what the root holds and not the root, whose availability check runs on the roots' I/O thread. Nothing runs on the loop; a stat added there makes it fail again. The node CLI's parser names itself: under `python -m pytest`, Python 3.14 named the program after the module, so test_cli_golden saw "python -m pytest" in the help. The installed meshbay-node was not affected. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(node): a removal made at the machine is in audit.dbChristophe Besson23 hours1-0/+27
| | | | | | | | | | | | The Node page and the CLI remove access through the loopback API, and revoke and unpin there wrote nothing to audit.db; only the signed MNP door did. A removed person's next connection then showed an auth_failed with nothing before it to explain it. Both routes now record member_revoke and member_unpin once the operation has succeeded, with the node's own account as the actor and 127.0.0.1 as the address, which tells the two doors apart. A refusal changes nothing and records nothing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(packaging): ship only the TMDB token line of QE/default.envChristophe Besson23 hours1-0/+74
| | | | | | | | | | | | | | build-node.sh and build-node-runtime.ps1 copied QE/default.env whole into every deb, rpm and Windows build after checking only its first 30 bytes. The file is unversioned and sits beside other QE notes, so any line added to it later would have gone out publicly. Only its first line ships now, and only once it is exactly MESHBAY_TMDB_DEFAULT_TOKEN=<JWT>; a missing file, a BOM, a token not on the first line or an empty one still stop the build. The Linux step is run by a test against a scratch repo; the PowerShell one has the same rule and was not run here. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* refactor(chat): no thread_id — chat has no threadsChristophe Besson25 hours2-18/+36
| | | | | | | | | | | | | | Nothing ever set it (the page and the CLI sent null) and nothing read it (the node's get_thread had no caller). It is no longer sent by the page or the CLI, in clear or in the sealed payload, nor relayed or served by the node; get_thread and its index are gone, and the protocol and design docs no longer name it. The node still accepts a chat_msg carrying one, from the desktop apps and CLIs already installed, and drops it. The column stays, empty, in chat.db: a node downgraded to an older version names it in every query. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(node): an operator who was a member first holds the authority at onceChristophe Besson25 hours1-0/+44
| | | | | | | | | | | | | | | A member pinned on the node who then redeems the operator pairing code was written down as the operator but the node kept its "nobody can authorize" flag until it restarted: every admin operation — removing a folder, inviting, the apps' settings — was refused meanwhile. Only a key paired fresh set it. The join result now names `operator` for an account holding the node-wide operator row, whatever its row in the group says. The page and the CLI read that role to know what to offer, and refused before asking deletions the node would have accepted. Nothing is granted by it: every admin operation is still checked against the operators' keys. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat(hub): one account, several linked nodesChristophe Besson28 hours3-4/+187
| | | | | | | | | | | | | | | | | | | | | | | | | An account links up to MAX_NODES_PER_ACCOUNT node keys (node_keys table) instead of the one users.pk_node_ed25519; the migration carries each account's key over and the downgrade keeps the last one linked. /v1/nodes/auth accepts any linked key, and its token names the key that won it (nk). PUT /me/node_key adds a key, idempotently, 409 past the limit; DELETE /me/node_key names the key: a browser session any of its own, a node only its own. Unlinking a key closes that node's socket and refuses its tokens at once, on REST and on the socket; the account's other nodes are untouched. /pubkeys no longer gives a node key to other accounts, and gives the caller its last linked one, which installed desktop apps read. The node reports waiting_for_node_key while its key is unlinked and signs in again as soon as the hub says so, so a key linked again is back within seconds; unlinking itself signs in first to hold a token naming its key. Profile -> Linked nodes lists each node (connected, last seen, IP, linked since, This machine in the desktop app) with Copy and Unlink, and adds a key by hand. The Node page banner, Create group and the desktop app add this machine's key instead of replacing another's. Messages that pointed at Settings -> Link Node point at Profile -> Linked nodes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(node): a chat-epoch notice answers no request, not even the asker'sChristophe Besson37 hours1-0/+27
| | | | | | | | | | Revoking a device or removing a member opens a chat epoch, announced to everyone connected to the group, the asker included. Sent inside the handler, the asker's copy was stamped with its request's id and arrived as the answer to device_revoke, before the real one. It now leaves outside the reply context. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat(cli): meshbay-cli, a command-line client for membersChristophe Besson39 hours2-5/+34
| | | | | | | | | | | | | | | | 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>
* feat(node): accept a device link request before the proof (MNP 6.3)Christophe Besson39 hours2-85/+133
| | | | | | | | | | | A new device of a known account holds no identity the node pinned, so it cannot complete a handshake until a pinned device approves it: it now files device_add_request in the pre-proof window, as the desktop app's case already required. The request stays inert until countersigned; at most 10 are pending per account, the oldest dropped. A 6.2 node answers it "Handshake required", which a client reads as "cannot link here". Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(node): index, serve and write files past Windows' MAX_PATHChristophe Besson2 days3-6/+406
| | | | | | | | | | | | | | | | | | | | | | | | | | | With the system's default setting (LongPathsEnabled off) every call given a plain path of 260 characters or more refuses it, so a deep library was partly invisible: the walk could not list such a directory and its files never reached the members. The node now hands each call that touches a group's disk the extended form (paths.long_path), and keeps the plain path in the index, the hash cache and the log. A directory is listed under its long form and its entries named from the plain one (indexer.walk_files, wire._walk_dirs). Covered: the walk, stats and hashing, reconcile and the watcher, the folder list members see, serving over WebRTC and QUIC, deletion, uploads and their recovery, folder creation and removal, audio tags and the cover beside a track, photos, and a show's episode numbering. ffmpeg extends a long path itself and keeps the plain one. long_path now normalises before prefixing (the extended form skips the system's normalisation) and starts at 248 characters, where creating a directory is already refused. test_long_paths.py runs with the setting off: 15 of its 17 tests fail without the change. Checked by hand in the installed app with files at 312 to 380 characters. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat(node): skip a file the disk stops answering for, then pause its rootChristophe Besson2 days5-15/+546
| | | | | | | | | | | | | | | | | | | | | | | | | | A Windows tester's scan stuck for good on one directory of a large disk: one read that never returned held the indexer's only thread, and with it every root behind that one. - A disk call is timed by its silence, not its length: each directory entry listed and each megabyte read counts as an answer. After 60 s of silence the call is abandoned on its daemon thread, the file is noted unread and the scan goes on. - Three silent files in a row pause the root (not persisted). Resume skips the files already known to give no answer; a restart, a replug or a change to the file retries them. - The walk is per directory with os.scandir: a subdirectory that cannot be listed costs itself, and a listing that stops answering pauses the root at once. A scan no longer stats anything from the event loop. - The log names the file after 10 s of silence, the indexing band shows it, and Group settings shows the paused root, where it stuck and the unread files with their reason, on the node's machine only. - generate_http_api.py writes LF on Windows too. Verified with real kernel-blocked opens (an unacknowledged oplock) in the installed frozen app. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* test(node): run the connection processes on Windows, source and frozenChristophe Besson4 days3-0/+62
| | | | | | | | | | | | | | | | | | | | | | | | | Connection processes and the larger UDP receive buffer were written and measured on Linux; nothing had run on Windows, and `meshbay-node.exe webrtc-worker` had never run from a frozen build on any system. - test_connection_processes.py failed on Windows (8 tests): the selector loop the suite uses there cannot start a process. A test marked `daemon_loop` now runs on the daemon's own loop, chosen through pytest-asyncio's loop factories. - The build starts both frozen executables as connection processes and carries a connection through each (smoke-connection-process.py). The daemon's own smoke test never signs in to a hub, which is when the processes start. - The warning for a receive buffer below what was asked named net.core.rmem_max on every system; it names it on Linux only. Windows grants the 4 MB. Measured on an installed build in service mode, docs/WINDOWS-PORT.md §5.12: two processes at startup and on a live change, a browser's handshake, chat, a 200 MB download over 8 connections across both processes, an upload, a video stream, and stop, restart and start leaving no process behind. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* test(node): look for Node in the menu under the avatarChristophe Besson4 days2-8/+20
| | | | | | | | | | Node and Create group left the sidebar for the user menu. A source-reading test still looked for the old `showNode=` expression and failed everywhere; the installed application's end-to-end test waited for "Create group" in the sidebar and would have timed out. Both now check the menu, and the packaging guide says where the two entries are. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat: let the operator pause and resume a root's indexing (MNP 6.2)Christophe Besson5 days6-50/+1126
| | | | | | | | | | | | | | | A whole-root walk now checks its root before every file: removed or ejected, it stops instead of reading the drive to the end; paused, it finishes from the hash cache alone. A paused root is not watched, not swept by reconcile and survives a restart (roster), its indexed files stay served. Resume runs the reconcile sweep over the root, so entries and their enrichment are kept. Signed root_pause / root_resume over MNP, loopback routes, CLI `root pause|resume`, a pause button on the indexing dock and pause/resume in the roots table of Group settings. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(node): tell the Node page how many cores there areChristophe Besson5 days1-0/+11
| | | | | | | The page reads its settings from list_groups, which lacked cpu_count, so the field showed "of 1" and stayed at 1. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(node): a receive buffer that holds an upload's windowChristophe Besson5 days1-0/+38
| | | | | | | Uploads ran at 6.5 MB/s because the kernel dropped datagrams at the node's 256 KB socket. Asking for 4 MB gave 19.5 MB/s, no drop. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat(node): carry connections on several coresChristophe Besson5 days2-1/+282
| | | | | | | | connection_processes (default 1) starts that many processes for the WebRTC stack; sessions and keys stay in the node. Set from node.toml or the Node page, applied to new connections. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat: spread a large download over eight connectionsChristophe Besson5 days2-1/+82
| | | | | | | | | One WebRTC connection carries 5 to 6 MB/s whatever the line can do. A leased download of 64 MB or more now opens seven more to its node, and a lease serves the other connections of its device in its group. Measured through the whole stack: 5 MB/s before, 24 after. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat(node): log what a transfer moved when it endsChristophe Besson5 days3-0/+130
| | | | | | | One INFO line per lease: bytes, duration, rate, and the SCTP association's window, round trip, losses and T3 expirations. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat(photos): show a phone's clips in their albumsChristophe Besson6 days1-0/+80
| | | | | | | | Videos in a photo folder are thumbnailed by the node, counted and marked in the album, and played in the group's video player from the lightbox; the slideshow passes them by. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(node): stop aiortc's SCTP sender leaking bytes in flightChristophe Besson6 days1-0/+59
| | | | | | | | A chunk gap-acked then resent after T3 stayed counted for good; enough of them and the node sent nothing more on that connection. The photo backup stalled on it after a few dozen photos. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* test(node): a name an LGPL file declares itself is its own bindingChristophe Besson6 days1-0/+7
| | | | | | | | | | | The LGPL closure check excluded only names declared at the top level of the LGPL files, so keyring.js's `const state = () => ...`, local to createKeyring, read as a call to photo-sync.js's top-level `state` once that file was added. A name the file declares at any depth now shadows an AGPL global for that file. A real call into an AGPL module is still caught (checked by adding one to keyring.js). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat(packaging): the Store package declares what the NSIS scripts doChristophe Besson6 days3-70/+258
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | A test-signed install of the MSIX build, in the WindowsApps folder a Store install uses, showed that every script-made piece of the NSIS model breaks there, because each names the install folder and every update deletes it: the firewall rules went stale, the PATH entries piled up pointing at deleted folders, and the Startup-folder .vbs was refused ("Permission denied") right after sign-in. The network capabilities the manifest declared covered nothing: they make rules for sandboxed apps only, and a listener in the package still got the Windows firewall prompt. The package's own startup task was on by default, started the node whatever mode the Node page said, and ran the console executable, whose window stopped the node when closed. The package now declares what Windows then creates at install, carries across updates and removes with the app, all without an administrator prompt (each measured on the real install, through an update and a reboot): - firewall rules for the node, in a custom manifest template, since only a package-level element can hold them; - the startup task, off by default, running meshbay-nodew.exe, a new build of the daemon without a console; - an execution alias for meshbay-node.exe, so the app adds no PATH entry. The node's CLI switches the startup task (platform.startup_task, ctypes over the WinRT ABI): Windows gives the package's identity to the executables in it, not to a powershell.exe the app starts, which got "Element not found". `meshbay-node autostart install | remove | status` therefore works in the Store package from the app and a terminal alike; the app caches the answer, since the Node page polls. Starting at boot stays the .exe installer's: the Store package offers no service mode, and the CLI refuses `service install` there. Process listings count both image names. The Node page's status poll cleared the message of a refused action within five seconds; the two errors are kept apart now (all Windows builds). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat(node): refuse an upload on a full disk with a stated reasonChristophe Besson7 days1-0/+134
| | | | | | | | | | | | Nothing on the upload path knew about ENOSPC: a write that found no room raised out of the handler, the catch-all answered "Request failed", and the .part stayed behind holding the space that had run out. The node now refuses with `disk_full` at chunk 0 when the announced size would leave less than 1 GiB free, and at any write that fails with ENOSPC/EDQUOT, dropping the partial. The client carries the code on the error and the transfers panel says "The node's disk is full" in every catalogue. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat: let the operator purge a group's chat (MNP 6.1)Christophe Besson7 days2-36/+534
| | | | | | | | Signed chat_purge from the Chat settings deletes every stored message; epoch keys and attachments stay. The ack is broadcast so open chat panels empty. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* perf(node): sample 9 MB with the size above 9 MB, keep known ids0.19Christophe Besson9 days2-33/+57
| | | | | | | | hash_version 3: size + first 4 MB + last 4 MB + 1 MB at the middle, 5.5x faster cold on a USB disk than the 45 MB sample. The cache now serves a hit under whatever version it holds, so no existing id moves. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(node): name a root after its drive when its basename is takenChristophe Besson9 days2-0/+47
| | | | | | | | | Two drives with a folder of the same name made the second add fail, and no screen could supply another name. add_root now names it "Name (H)" or "Name (parent)"; a name the operator typed is still refused on a clash. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(client): stop the node at Quit in "only while open", whoever started itChristophe Besson9 days2-6/+56
| | | | | | | | | | | | | | | | | | | Switching from "at sign-in" to "only while MeshBay is open" left the node the sign-in launcher had started running after Quit: only a node this process had started was stopped. In that mode the app owns the node, so Quit stops the one that is there. The start with the app and the sign-in's own start (ensureNode) also both ran `autostart start` at launch -- three meshbay-node.exe were seen racing for the port. The sign-in's start and node:start now wait for the launch's. The end-to-end test covers the mode: Quit leaves no node, opening the app starts one. It launches the app with the environment it was imported with: the suite's conftest points HOME, USERPROFILE, LOCALAPPDATA and APPDATA at a throwaway directory per test, and the app started under that crashed at once (0x80000003), which first looked like a crash of the app itself. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* test(node): check the startup mode in the Windows end-to-end testChristophe Besson9 days1-5/+22
| | | | | | | | The boot task or the sign-in launcher, as chosen, and the node in session 0 for the service's S4U logon or in the signed-in session otherwise. Passed in service and sign-in modes on the installed 0.18.0 build. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix: set the Windows node up at sign-in, and stop it for realChristophe Besson9 days3-1/+525
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Found by the first Windows beta tester, then reproduced on a clean install. After a service-mode install nothing set the node up for the account that signed in: the boot task started a node that quit ("hub.username not set"), and the sidebar showed Node / Create group only once the hub held a node key. The only way to the wizard that provisions was the home page's welcome card, which an account already in a group never sees. The way out was `meshbay-node init` and the key pasted on the profile page -- which is also what PACKAGING-GUIDE.md told people to do. - main.js `node:ensure`, called by app.js at sign-in: provisions, starts and links the node this build ships (Windows, bundled node only). A node set up for another account, or an account linked to another node, is left alone. node:start waits for it, so the two never race. - The sidebar shows the Node section when a node exists on this machine. - The Node page's status is the node's: its control API and the process list, not the service task's state (a node started from a terminal ran while the page said Stopped). Stop says Stopped only once no meshbay-node.exe is left, and stays offered for a process that answers nothing. - CLI stop kills the pid that answered when a graceful stop does not finish, and fails with the reason when a node process is still there. - The daemon ends its process 3s after _shutdown(): Python's exit waited for a busy indexer thread, with the control API already closed. Armed by main() only, never by a daemon run inside a test. - node.toml is read as utf-8-sig (PowerShell 5.1 writes a BOM), and a config that cannot be read is logged instead of dying silently in service mode. - "Pair this browser" queues the code for the next group of this node to open instead of saying "Paired successfully"; no banner before a group. - test_e2e_windows_app.py (opt-in, MESHBAY_WIN_E2E=1) drives the installed app against a throwaway hub: fresh account to linked node, Stop, Start, Restart, checked against the real processes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(node): drop a directory moved out of the root on Windows tooChristophe Besson9 days1-0/+13
| | | | | | | | | On Windows watchdog cannot tell what a deleted path was -- it is gone -- and reports a directory moved out of the root as a file deleted, so the fix in 3d5a168 never ran there and test_the_watcher_reports_a_directory_moved_out failed. A deleted path the index still holds entries below was a directory. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* test: read UTF-8 explicitly in the licensing and keyring-vector testsChristophe Besson9 days1-25/+41
| | | | | | | | | | On Windows a bare `read_text()` or `subprocess.run(text=True)` decodes with the locale's code page (cp1252), not UTF-8. `test_licensing.py` then failed on a byte of the vendored LICENSES.txt, and `test_keyring_vectors.py` decoded the generator's output, which carries CJK test strings, into something that no longer matched keyring.json. Both files are UTF-8; say so. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(node): name members admitted without an invitation nameChristophe Besson9 days1-0/+36
| | | | | | | | | | | A member who joined by link, by a new device or into an open group was pinned in the roster with no name, so the audit log showed only the first characters of their id. The hub's MNP token now carries the account's username, and after the handshake the node writes it into the roster for an account whose name is empty. An invitation's name is never overwritten; the name is a label, authority stays on `sub`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* perf(node): list a group's directories off the event loopChristophe Besson9 days3-93/+89
| | | | | | | | Every full index walked all roots on the loop, and a node with several large roots stopped answering for seconds. Walk directories only, on the roots' disk thread; index_sync is spawned and still answers on failure. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(client): remove firewall rules and boot task on uninstall, unaskedChristophe Besson9 days1-11/+20
| | | | | | | The Yes/No before it defaulted to No, so they stayed behind. Read both unelevated and raise the UAC prompt only when one is left. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(node): drop a directory moved out of the root from the indexChristophe Besson9 days1-0/+97
| | | | | | | | Watchdog reports such a move as one "directory deleted" event and nothing for the files, which the indexer ignored until the next reconcile. The freeze rules still apply: root live, directory gone, parent present. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(node): prefer the bundled ffmpeg over PATH in the frozen buildChristophe Besson9 days1-0/+36
| | | | | | | | shutil.which never looks beside meshbay-node.exe, and node-runtime is appended to the user PATH, so any earlier ffmpeg.exe on PATH ran instead of the pinned copy. Log the resolved media tools at startup. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(node): plug an auto-ejected removable root back once its files returnChristophe Besson11 days1-0/+128
| | | | | | | | | | | | | | | | | A node started with the desktop session runs before the session has mounted its USB drives. The safety net then auto-ejected every removable root and persisted it exactly like an operator's eject, so after each reboot those roots stayed ejected until someone plugged them by hand (seen on a node whose /media drives were mounted a minute after it started). An auto-eject is now stored as such ("auto" in roster.db). At startup and at every reconcile, an auto-ejected root whose path is readable again is checked against a few files the hash cache knows under it, at the same path with the same size and mtime; one found and the root is plugged back and rescanned. An empty mount point or another drive in its place is not recognised and stays ejected. An operator's eject is never undone automatically. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* chore: license MeshBay — LGPL protocol layer, AGPL for the restChristophe Besson11 days1-0/+257
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | The protocol layer is LGPL-3.0-or-later in every language it exists in, so any client may use it whatever its own licence: meshbay-common, and the files marked with an SPDX line — keyderive.js, crypto.js, playlist-crypto.js, transport*.js; keyring.js, transcripts.js and argon2-wasm.js on the desktop; Kdf.kt, Keyring.kt and Transcripts.kt on Android. Everything else is AGPL-3.0-or-later, which the RPM specs and package.json already declared without a licence file to back them. Two AGPL section 7 permissions: - group applications may be under any licence when they use the interface only through a named surface (static/licenses/APPLICATION-EXCEPTION.txt); the reference application is 0BSD so that copying it brings no AGPL code; - the Android application may be conveyed linked with Google Play services. Third-party code is accounted for: THIRD-PARTY-NOTICES.txt is generated from what a build ships (packaging/third_party_notices.py) for the deb/rpm venv and the frozen Windows node — PyAV's wheel grafts in libx264 and libx265, which its BSD licence does not mention — and the vendored browser libraries get their licence texts and htm-preact.js its provenance. Wheels carry SPDX metadata, RPMs %license, debs a DEP-5 copyright file, every Windows target LICENSE.txt. test_licensing.py holds the line: the LGPL layer imports nothing under the AGPL, the reference application nothing outside the application interface, and every SPDX line is one of the known ones. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Merge branch 'main' of meshbay.org:meshbayChristophe Besson2026-10-027-5146/+257
|\
| * refactor(mnp): remove ten operator messages no client sent0.17Christophe Besson2026-10-024-3850/+94
| | | | | | | | | | | | | | | | | | node_status, node_settings_set, roster_read, denylist_read, denylist_clear, node_reload and the signed gek_rotate, member_unpin, transfer_limits, group_detach leave MNP 6.0; the Node page and the CLI do this work over loopback. Their ops keep their tests, moved to the ops level. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
| * fix(client): no confirmation dialog for adding a folder or its flagsChristophe Besson2026-10-021-1/+1
| | | | | | | | | | | | | | Removes confirmFolder (addRoot, attachGroup) and the writable confirmation added in e4f6177, with their two catalogue keys. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
| * feat(mnp): sharing a folder is decided on the node's machine only (MNP 6.0)Christophe Besson2026-10-025-1310/+177
| | | | | | | | | | | | | | | | | | | | root_add, root_update and group_attach leave MNP: adding a directory and switching writable/removable go through the loopback API (native dialog in the desktop app) or the CLI. The operator's Settings tab still lists the roots from any browser, read-only. The desktop app refuses to sign those ops; a loopback flag change now reaches open pages (publish_roots). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* | test(node): node.toml fixtures use POSIX paths, and a Windows-impossible ↵Christophe Besson2026-10-022-8/+13
|/ | | | | | | | | | | folder is skipped The root fixtures wrote `path = C:\Users\...`, which is not valid TOML: node_toml now reads values with tomllib, so the four tests failed on Windows. The node and the app always write paths with `/`, as the other fixtures do. A folder named with a quote and a newline cannot exist on Windows. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(node): the clear fields beside a chat message are boundedChristophe Besson2026-10-011-0/+35
| | | | | | | | sender_name and thread_id travel in clear beside the sealed envelope and were stored and relayed whatever their type and size. A name longer than a username or a thread id that is not a short id is now dropped (F-27). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(node): a member is told a media tool failed, not what ffmpeg saidChristophe Besson2026-10-011-0/+22
| | | | | | | | Stream, transcode and subtitle failures sent the exception's text — operator paths, versions — to the member. Fixed messages now, the cause in the log (F-24). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(node): the reaper deletes only the .part files the node wroteChristophe Besson2026-10-011-10/+22
| | | | | | | | Any *.part older than a day in a writable root was deleted — a browser's download in progress in a shared folder included. Only names carrying the node's tag (name.<8 hex>.part) are reaped now (F-28). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(node): a revoked account is disconnected, not only refused next timeChristophe Besson2026-10-011-0/+53
| | | | | | | | A user revocation closed nothing: the denylist stopped the next connection and left the live ones streaming and chatting. Revocations now go through one method that closes the account's or the group's sessions (F-21). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>