fluxy

fluxy-rsAGPL-3.0活跃维护

Software for fast file transfer between terminals in the same network segment.同网段中终端间快速互传文件

v0.1.17发布于 2024-12-13 05:21 UTC492star +1 / 7 天42fork同步于 2026-11-10 23:57 UTC

怎么上手

从 README 代码块原样提取,可能是其中一种安装方式
bun install
bun run build          # frontend production build
bun run tauri:dev      # desktop dev shell
cargo run -p fluxy-cli -- serve --listen 0.0.0.0:0 --data-dir ./.fluxy-a
cargo run -p fluxy-cli -- discover --timeout 3
cargo run -p fluxy-cli -- interfaces          # every adapter, with the automatic pick marked
cargo run -p fluxy-cli -- serve --interface "Wi-Fi" --data-dir ./.fluxy-a
cargo run -p fluxy-cli -- serve --address 192.168.1.20 --data-dir ./.fluxy-a
硬件要求按 README 关键词推断
支持 Apple Silicon
下载:源码 zip·GitHub Releases(安装包)本站不镜像安装包

项目说明

这个项目的说明还在生成中,先参考下面「最近发版」和 README。

最近发版

5 个版本v0.1.17
  1. v0.2.0-alpha.1pre-release2026-11-09 · 2026-11-09 14:23 UTC
    查看更新说明
  2. v0.1.17最新正式版2024-12-13 · 2024-12-13 05:21 UTC

    🎉 Features

    • feat(i18n): add macOS-specific locale handling(#279)

    🐛 Fixes

    • fix: Show and focus main window on startup to prevent white screen flash(#274)

    📦 Dependencies

    • chore(deps): upgrade dependencies(#280)
  3. nightlyNightly buildpre-release2024-12-13 · 2024-12-13 01:23 UTC
    查看更新说明

    [!WARNING] 每夜版可能无法自动升级。/The nightly version may not auto-update.

    Last Commit

    Merge pull request #281 from alley-rs/0.1.X

    fix(i18n): resolve locale issue on Linux systems

  4. v0.1.162024-11-11 · 2024-11-11 12:55 UTC
    查看更新说明

    🎉 Features

    • feat: support Engligh(#268)

    📦 Dependencies

    • chore(deps): update dependencies(#266)
    • chore(deps): upgrade dependencies(#269)
  5. v0.1.152024-09-28 · 2024-09-28 13:41 UTC
    查看更新说明

    🚀 Enhancements

    • chore: 修改windows安装器语言为中文,更新文件类型为 nsis(#259)

    📦 Dependencies

    • chore(deps): 升级依赖(#262)

用户点评

登录用户的真实使用记录,可按版本和硬件筛选
共 0 条

    还没有人点评。第一个说出真实体验的人最有价值。

    README 原文

    未翻译

    Fluxy

    Fluxy is a local-first, LAN-only desktop and mobile transfer tool. It provides device discovery, text chat, clipboard sharing, and resumable file/folder transfer without an account, cloud relay, or persisted chat history.

    This repository contains the v0.2.0 desktop implementation and the Android v0.3.0 track.

    Documentation

    DocumentWhat it covers
    User guideInstalling, pairing, chat, clipboard, transfers, settings, troubleshooting
    Known issuesWhat this build does not do yet, with workarounds
    PackagingInstallers, signing, clean-machine checklist
    Release matrixWhich check is verified on which platform, and how to reproduce it
    Release checklistThe gate list for tagging a release
    Release notes 0.2.0What shipped and what to expect
    Firewall guidePer-platform rules and the usual connectivity failures
    Security modelThreat model, controls, and the verification evidence
    Security auditDependency policy, advisory status, unsafe inventory, fuzzing
    TestingEvery suite, what it proves, and how to run it
    Implementation statusMilestone-by-milestone record with test counts
    ProtocolFrames, handshake, transfer negotiation, chunk format
    DesignProduct and architecture decisions
    Development planMilestones, estimates, and acceptance criteria
    Mobile planThe Android track
    OpenHarmony planThe HarmonyOS NEXT track (experimental, branch-gated)

    A release candidate is soaked with bun run soak --minutes 60, which pairs the CLI once and then loops chat plus a hashed file transfer, recording every round in .soak/log.jsonl.

    Requirements

    • Rust stable (edition 2024, MSRV 1.85)
    • Bun 1.4+
    • Tauri 2 system prerequisites for your OS

    Commands

    bun install
    bun run build          # frontend production build
    bun run tauri:dev      # desktop dev shell
    bun run tauri:build    # desktop bundle
    bun run check          # TypeScript contract check
    bun run check:security # CSP/capability/sink regression check
    bun run test:e2e       # cross-platform CLI acceptance (pair, chat, clipboard, restart)
    
    cargo check --workspace
    cargo test --workspace
    cargo run -p fluxy-cli -- status --data-dir ./.fluxy-dev
    

    The CLI is the reference consumer of the engine during the early milestones. It supports id, status, config, discover, interfaces, serve, pair, chat, clipboard, and the legacy combined connect command.

    Discovery

    A non-loopback listener advertises _fluxy._udp.local. over mDNS and prints discovery=mdns. A second machine on the same LAN can browse for it:

    cargo run -p fluxy-cli -- serve --listen 0.0.0.0:0 --data-dir ./.fluxy-a
    # listen=0.0.0.0:xxxxx
    # device=<server device UUID>
    # fingerprint=<64 hex characters>
    # discovery=mdns service=_fluxy._udp.local.
    
    cargo run -p fluxy-cli -- discover --timeout 3
    # peer device=<server device UUID> name=... platform=... addr=... port=xxxxx version=1
    

    Use --interface <name> on serve or discover to restrict multicast to one interface, and --json on discover for scripted output; JSON includes the certificate fingerprint as a pin hint. Discovery records are hints only; pair is still required and the QUIC/TLS handshake remains authoritative.

    Omitting --listen on serve hands the choice to the interface selection: --interface (with an optional --address) pins one adapter and address, and otherwise the automatic pick takes the interface that carries the default route, prefers a real adapter over a VPN or hypervisor one, and re-checks every two seconds so a moved cable or a newly joined Wi-Fi is followed. The listener socket is pinned to the chosen interface where the platform supports it (Windows IP_UNICAST_IF, macOS IP_BOUND_IF, Linux SO_BINDTODEVICE), and the serve line prints selection=, listen=, and interface_pinned= so a script can see what happened:

    cargo run -p fluxy-cli -- interfaces          # every adapter, with the automatic pick marked
    cargo run -p fluxy-cli -- serve --interface "Wi-Fi" --data-dir ./.fluxy-a
    cargo run -p fluxy-cli -- serve --address 192.168.1.20 --data-dir ./.fluxy-a
    

    In the desktop app the same choice is a persisted preference (network in config.json) and Settings → Network interface drives it through set_network_selection. The panel shows the active interface, why it was chosen, whether the socket is pinned, and how often a network change has rebound the listener, then lists every adapter with its kind, index, addresses, and the recommended/default-route/virtual marks. Switching mode or adapter asks for confirmation first, because it drops current sessions and pauses transfers in flight (they resume from their checkpoint). A manual pick that disappears is reported as error 2003 with the recovery steps - choose another interface, or plug the network back in - and the app never downgrades to a different address on its own. A copy button writes the interface list and addresses as text for support.

    Loopback is never advertised. A listener on a loopback address prints discovery=disabled-loopback; direct loopback pairing remains available only through the explicit --dev-loopback flag.

    File access

    The transfer layer never sees platform paths. fluxy-core::files defines FileHandleId, FileSource, FileSink, and FileAccessManager; the desktop implementation registers FileReference::Path sources and writes received files to receiver-root temp files that only become visible after every byte is covered. Boundary tests:

    cargo test -p fluxy-core files
    

    fluxy-core::transfer adds the manifest model used before a transfer: a parallel BLAKE3 window pass produces per-block hashes, the file root hash, and a manifest digest, and an identity-bound on-disk cache skips re-hashing when the source is unchanged. Run the focused tests and the ignored 10 GiB gate with:

    cargo test -p fluxy-core manifest
    FLUXY_BENCH_FILE=/path/to/10gib.bin cargo test -p fluxy-core --release --lib benchmark_10gib_prehash_under_15s -- --ignored --nocapture
    

    The receiver then pulls the file with a bounded window: fluxy-core::transfer owns the chunk bitmap and the pull scheduler (16 outstanding requests, 15 s retry deadline, 3 attempts), and fluxy-net::transfer serves each request on the transfer's own stream as an FCH1 frame and hash-verifies every chunk against the manifest before writing it. Each transfer names its stream with its transfer id before anything else, so several files can move over one connection without swapping streams. Loopback tests exercise concurrent transfers, out-of-order duplicates, repeated transfers on one connection, and the missing-chunk failure path:

    cargo test -p fluxy-core transfer
    cargo test -p fluxy-net transfer
    

    Resume state is stored per transfer under checkpoints/<transfer-id>/: a binary manifest.bin and a checksummed progress.bin holding the role, state, source identity, and received-block bitmap. Both are written through an fsynced temporary and an atomic rename, and the receiver reopens the deterministic .fluxy-<transfer-id>.part file through resume_sink so only missing chunks are pulled. A torn or mismatched checkpoint restarts the transfer instead of stitching two content versions:

    cargo test -p fluxy-core checkpoint
    cargo test -p fluxy-net transfer::tests::sender_crash
    cargo test -p fluxy-net transfer::tests::receiver_crash
    cargo test -p fluxy-net transfer::tests::receiver_task_abort
    

    A transfer can also be paused, cancelled, or retried. fluxy-core::transfer owns the enforced lifecycle (StateMachine) and a cloneable TransferControl handle; the receive loop polls it every 100 ms, so a pause freezes the request window without consuming retries, and a cancel returns 3008 Canceled, moves the state to cancelled, discards the .part file, and removes the checkpoint:

    cargo test -p fluxy-core state
    cargo test -p fluxy-core control
    cargo test -p fluxy-net transfer::tests::pause_then_resume
    cargo test -p fluxy-net transfer::tests::cancel_from_paused
    

    A completed pull is verified before it becomes visible. Every chunk is checked against its payload hash and the manifest block hash, and a mismatch is rejected by block index and retried instead of written. Once the last block lands, the receiver streams a BLAKE3 hash over the partial file and compares it with manifest.root_hash; only a match moves the state to completed and renames .fluxy-<transfer-id>.part into place. A mismatch fails with 3005 HashMismatch, drops the checkpoint, and discards the partial file, and the sender re-stats the source before each request so a file changed mid-transfer stops the transfer with 3002:

    cargo test -p fluxy-core files::desktop::tests::root_hash
    cargo test -p fluxy-core scheduler::tests::a_rejected_chunk
    cargo test -p fluxy-net transfer::tests::a_tampered_chunk
    cargo test -p fluxy-net transfer::tests::a_source_changed
    cargo test -p fluxy-net transfer::tests::a_root_hash_mismatch
    

    The transfer center (/transfers) is the user-facing side of all of this. It sends a file through the native picker and a trusted-device chooser, prompts for an incoming offer before anything is written, and shows per-task progress, size, speed, and remaining time, with pause, resume, cancel, and retry. Progress is throttled in the backend to at most one update per 100 ms per transfer, so a fast transfer cannot flood the UI, and terminal events are never dropped.

    Every task has a Details dialog: state, direction, peer, progress, speed and remaining time, creation and update clock, and — for a failure — a localized reason, the protocol code and its number, whether the core considers the failure retryable, and the raw message. Reasons are resolved from the stable error codes (src/lib/transfer-errors.ts); a code from a newer peer degrades to a neutral message instead of a raw key. Retry is offered where the core says the failure can be repeated (a changed source or a hash mismatch asks for a new send instead) and works straight from the dialog. Copying the details yields the same fields as text, and the filesystem path never leaves Rust:

    cargo test -p fluxy transfer::tests
    bun run test:frontend
    

    Files can also be dropped straight onto the window or picked in one batch. Every file becomes its own transfer, so one failure never blocks the rest. At most 100 files are queued per send, and the number of transfers running at the same time is capped (default 2, adjustable from the transfer center) — everything else waits in the queue, and a paused transfer keeps its slot. A device that is already at its cap answers a new offer with 2002 PeerBusy instead of starting a transfer it cannot serve. The drop itself is handled in Rust, so the WebView receives opaque handles and base names rather than local paths:

    cargo test -p fluxy transfer::tests
    cargo test -p fluxy-app
    bun run test:frontend
    

    Path, overwrite, and space policy hardening lands in M2-10.

    Local development

    Two CLI instances can pair over loopback. Start the listener, copy the printed device id, address, and fingerprint, then pair from the second terminal:

    cargo run -p fluxy-cli -- serve --listen 127.0.0.1:0 --dev-loopback --data-dir ./.fluxy-a
    # listen=127.0.0.1:xxxxx
    # device=<server device UUID>
    # fingerprint=<64 hex characters>
    
    cargo run -p fluxy-cli -- pair 127.0.0.1:xxxxx \
      --fingerprint <64 hex characters> --dev-loopback --data-dir ./.fluxy-b
    

    Both sides print the same six-digit SAS and wait for yes on stdin. The certificate is pinned by fingerprint, and both trust stores are written only after the SAS is accepted. Once paired, send chat in a later process without another SAS prompt:

    cargo run -p fluxy-cli -- chat 127.0.0.1:xxxxx \
      --device <server device UUID> --message "hello" --dev-loopback --data-dir ./.fluxy-b
    

    chat sends UTF-8 text (at most 32 KiB) over the control stream and waits for the application-level MESSAGE_ACK. The legacy connect command combines pairing or resume with an optional --message for compatibility.

    Clipboard sharing is a two-step exchange. The sender offers a preview, size, and BLAKE3 digest; the receiver must accept before the text is sent:

    cargo run -p fluxy-cli -- clipboard 127.0.0.1:xxxxx \
      --device <server device UUID> --text "hello" --dev-loopback --data-dir ./.fluxy-b
    

    clipboard waits up to 90 seconds for the receiver's decision, then prints clipboard-applied only after the final ACK. The text is capped at 1 MiB and is never written to disk or logs. A headless serve has no consent UI and rejects offers with cli_no_consent.

    File transfer is a receiver-driven pull over the same session. transfer-send offers one file, transfer-recv accepts it and reports the final root hash:

    cargo run -p fluxy-cli -- transfer-recv --listen 127.0.0.1:0 --dev-loopback --data-dir ./.fluxy-a
    cargo run -p fluxy-cli -- transfer-send 127.0.0.1:xxxxx \
      --device <server device UUID> --file ./payload.bin --dev-loopback --data-dir ./.fluxy-b
    

    Both print FLUXY_XFER key=value lines so a script can follow progress and read root_hash, bytes_written, and resumed. A retry after an interrupted run reuses the checkpoint when the same --transfer-id is passed; transfer-recv takes the id from the offer.

    Pass --once to serve when a single connection is enough for a scripted test. TLS certificate/key material is stored under the data directory, so the server fingerprint and the client certificate pin stay stable across restarts. Chat is in-memory only; no conversation is written to disk.

    Loopback binding and targets require an explicit --dev-loopback flag; loopback is never advertised over mDNS and is not a production network mode.

    Desktop shell

    bun run tauri:dev starts the Tauri shell and the Solid frontend. The shell uses a hash router so a packaged window can reload any route without a server fallback: / (devices and session), /transfers, /clipboard, and /settings. The layout is a dark instrument rail, a ruled workspace, and a status bar on desktop; at 768px and below the rail collapses to a bottom navigation.

    The theme follows the system by default and can be pinned to light or dark from the top bar or Settings. Locale defaults to zh-CN and can switch to English; the document lang and window title follow the choice. Interface copy lives only in src/i18n, and a catalog test fails the build when the two locales drift, when a placeholder disappears from a translation, or when a component hardcodes copy. Settings also edits the device name (1-64 characters; saving restarts the LAN advertisement so other devices see the new name right away) and turns desktop notifications on or off. The main flow is reachable with the keyboard alone: a skip link jumps to the content, the rail, pairing dialog, offer dialog, and composer are native controls, dialogs trap focus and close on Escape, and closing a dialog returns focus to the control that opened it. All route states share the same empty, loading, and error primitives, and prefers-reduced-motion turns transitions off.

    Nearby devices appear automatically on the rail and home page. Pairing opens a dialog with both device names, the same six-digit SAS on each screen, a live 120-second countdown, and an explicit warning to compare both screens. The confirm button stays disabled until the acknowledgement checkbox is ticked; Fluxy never auto-confirms. There is no QR or manual-address entry. Successful pairing writes trust, and Settings can remove a trusted device or show discovery diagnostics (interface, listener, mDNS state, and nearby count).

    Discovery prefers mDNS. When a phone or Windows hotspot filters multicast, the desktop also sends a small UDP announcement to the limited broadcast address and each directed subnet broadcast, and probes the default gateway by unicast. A peer found this way is still only a hint: the normal TLS certificate pin and six-digit SAS confirmation decide trust. Settings shows whether the fallback is active, the gateway and targets, probe/reply counters, and fallback-only peers.

    The home view also contains the memory-only chat workspace: a conversation list of trusted devices, a message stream, a composer, and per-message delivery state. Sending opens a trusted resume with the selected peer; an inbound trusted resume becomes a chat session. A message stays Sending until the peer's application ACK arrives, becomes Failed with a retry action if the session ends first, and a retry reuses the original message id so the receiver does not deliver it twice. The transcript is capped at 1000 messages, is never persisted, and is cleared on exit or identity rotation.

    The clipboard route lists online trusted peers and sends the current system clipboard only after Send current clipboard is clicked. The sender reads the clipboard at that moment, sends a preview/size/digest offer, and sends the text only after the receiver accepts. Incoming offers show the peer, preview, size, and a countdown; nothing is written to the system clipboard until Accept. Accepted text is verified against the offer digest, and the history keeps only in-memory previews and status. Each trusted device has a persisted receive policy: Ask (default) shows the incoming queue, Auto requires an explicit per-device selection and writes without a prompt, and Reject declines silently.

    Auto send is the other, separate switch and is off by default. When it is on, the backend reads the system clipboard every 300 ms and offers a value only after it has looked the same for a full debounce window, so a half-written clipboard or a burst of copies produces one offer instead of many. Offers go to every online trusted device and the receiver still applies its own receive policy. The loop rules are what make it safe to leave on: offers carry an origin id and a hop count, a receiver never forwards, and a digest that the local clipboard applied from a peer is never offered back - so a device that just received text does not echo it to the sender even though it is now on its clipboard. The same content is never auto-sent twice, only text is synced, and the 1 MiB cap still applies. Turning the switch off stops all clipboard reads, and turning it on records whatever is already on the clipboard instead of sending it, so enabling never broadcasts a password copied a moment earlier.

    Install

    Release builds ship an installer per platform; the Windows build produces NSIS and MSI packages, macOS a .dmg, Linux an AppImage and a deb. The Windows installer is per-user (no elevation) and installs in English or Simplified Chinese.

    bun x tauri build --bundles nsis     # target/release/bundle/nsis/*.exe
    pwsh -NoProfile -File scripts/package/windows.ps1
    

    Packages are unsigned by default; signing (Windows certificate, macOS notarization) and the clean-machine install checklist are in docs/PACKAGING.md. Uninstalling removes the program and the tray entry but keeps %APPDATA%/com.thep0y.fluxy, so reinstalling does not orphan the device identity or its pairings.

    Platforms

    Fluxy targets Windows, macOS, and Linux. The CI matrix runs format, lint, the full test suite, the decoder fuzz harness, and the CLI acceptance scenario on all three, and tauri build --no-bundle on demand; docs/RELEASE_MATRIX.md records which row is verified where and which ones still need a host of each platform.

    Security checks

    cargo deny enforces the license allow list, a crates.io-only source rule, and a ban on wildcard dependencies (deny.toml); the decoder fuzz harness runs on every cargo test and can be pushed to millions of mutations; and bun run audit runs both plus an inventory of the five unsafe blocks in the workspace. The results, the two license decisions, and the known items - including the advisories check that needs network access - are in docs/SECURITY_AUDIT.md.

    Performance

    Transfers are measured, not estimated. The repeatable benchmark moves 256 MiB through two endpoints over loopback on one transfer stream and writes the received file to disk:

    cargo test -p fluxy-net --release --lib benchmark_loopback_throughput -- --ignored --nocapture
    

    It reports roughly 240-265 MiB/s on a desktop CPU, which comfortably covers a 1 GbE link (105 MB/s) but is below the plan's 1.0 GiB/s loopback goal: the limit is one UDP socket's packet rate and the per-chunk control round trip, not bandwidth or copying, so closing that gap needs multiple sockets or GSO/GRO rather than tuning. Stream count, window size, MTU, socket buffers, and per-chunk copies were all swept and none of them moved the number by more than noise; docs/TESTING.md records the measurements.

    A folder can be sent as a batch: drag it onto the window, or use Send folder on the transfer page, and the confirmation names the root, the file count, the empty-directory count, and the total size before anything moves. The receiver rebuilds the tree under its own receive directory - empty folders included - and refuses any relative path that would escape it, is absolute, contains a traversal segment, a control or bidirectional character, an alternate data stream, a reserved device name, a trailing dot, an empty segment, or a case-only duplicate. Every file in the batch stays an ordinary transfer, so it has its own progress, checkpoint, and retry, while the transfer page shows one collapsible row per batch with batch-wide pause, resume, and cancel. Symbolic links are refused rather than followed, and a batch is capped at 10,000 entries.

    A native tray icon restores a hidden or minimized window; its menu - Open Fluxy, the Auto send clipboard and Start at login switches, Help and firewall, and Quit - follows the selected UI locale, and the two check items mirror the persisted preferences. Closing the window no longer ends the process: the close request is prevented and the window is hidden, so chat, clipboard offers, and transfers keep arriving in the tray, and only Quit stops the listener. Start at login registers a per-user login item (Windows Run value, macOS launchd plist, Linux XDG autostart entry) that launches the app with --hidden, so a login start lands in the tray; the app repairs the item on the next start if something removed it while the preference was on. When the window is unfocused, incoming chat messages (and pending clipboard offers under Ask, or applied clipboard under Auto) raise an OS notification containing only the peer name and event type, never the message or clipboard text. A finished or failed transfer raises one too, naming the file and the peer; a user cancel stays silent, and a bulk send that finishes at once coalesces to a single notification per device instead of one per file. Settings can mute all of them. Clicking the notification or the tray icon restores and focuses the window. On Windows, native toast delivery requires an installed build; development builds may show PowerShell branding.

    Because the app is LAN-only, the operating system firewall has to allow its inbound UDP traffic: discovery uses a fixed UDP port and transfers use the port the listener binds (system-assigned by default). The in-app Help and firewall page and docs/FIREWALL.md list the per-platform rules, show the ports the running instance actually uses, and cover the usual "devices see each other but never connect" cases.

    The Rust adapter exposes engine_status, state_snapshot, events_subscribe, identity_rotate, pair_start, pair_confirm, pair_cancel, trust_remove, clipboard_set_policy, chat_open, chat_send, chat_retry, clipboard_share, clipboard_accept, clipboard_reject, set_auto_clipboard, set_autostart, set_network_selection, pick_folder, start_folder, shell_set_labels, discovery_diagnostics, pick_file, pick_files, register_file, start_transfer, start_transfers, set_transfer_concurrency, set_device_name, set_notifications, transfer_respond, transfer_control, and transfers_list through tauri-specta. The UI first loads a snapshot and then subscribes to one event Channel; each event carries a process-wide seq and a snapshot_version, and a gap triggers another state_snapshot instead of an incremental guess.

    The generated contract lives in src/bindings.ts and should not be edited by hand. Regenerate it after changing a command or IPC type:

    $env:FLUXY_EXPORT_BINDINGS='1'; cargo run -p fluxy
    

    Frontend component tests run under Vitest and jsdom with a typed mock engine context, so they do not need Tauri IPC: bun run test:frontend. bun run test runs the Rust workspace suite and then the component suite. The M1-09 acceptance scripts in scripts/e2e/ run the pairing, chat, clipboard-consent, restart/resume, and rejection path on Windows, Linux, and macOS; see scripts/e2e/README.md.

    Security

    The M0 threat model, high-risk decisions, and release gates are in docs/SECURITY.md. The review record is docs/security/M0_REVIEW.md. The M1-03 shell runs under a strict production CSP, uses only core:default in its main-window capability, has no opener plugin, and enables freezePrototype; bun run check:security guards those settings. mDNS certificate fingerprints are pin hints only; all first-pair trust still comes from the two-screen SAS comparison. The CLI and shell are still a development prototype: OS keystore-backed keys, global connection/message limits, and file path defenses remain release gates.

    Fault injection

    tools/udp-chaos forwards UDP through a lossy link so recovery can be tested without unplugging a network. It supports packet loss, fixed delay, jitter, duplication, reordering, and a one-shot outage:

    cargo run -p udp-chaos -- \
      --listen 127.0.0.1:42430 \
      --target 127.0.0.1:42420 \
      --loss 0.05 \
      --delay-ms 20 \
      --outage-after-ms 3000 \
      --outage-duration-ms 500
    

    Point pair and chat at the proxy address while the server keeps its real address and fingerprint. The proxy never creates trust and cannot bypass TLS/SAS. See docs/TESTING.md for the manual workflow and cargo test -p udp-chaos for the automated two-node recovery suite.

    Acceptance matrix and the 10 GiB benchmark

    scripts/acceptance/ drives the real CLI to cover the plan's T-01..T-06 matrix: pairing, a local transfer with matching root hashes, a link outage with resume, a receiver restart with resume, the fault-injection tests, and the benchmark with a receiver memory ceiling.

    node scripts/acceptance/acceptance.mjs local --size 64MiB
    node scripts/acceptance/acceptance.mjs matrix              # T-01..T-06, T-05 manual
    node scripts/acceptance/acceptance.mjs bench --size 10GiB --release
    

    The two CLI modes behind it are transfer-send and transfer-recv. T-05 (revoked file access) needs an ACL change and is a documented manual step in scripts/acceptance/README.md.

    License

    GNU Affero General Public License v3.0. See LICENSE.

    This is the strictest licence that is still an open-source licence, chosen for one reason: a modification has to stay open. AGPL section 13 extends GPL section 12 to network use, so running a modified Fluxy as a service and letting others talk to it counts as distribution - the source has to be offered, under the same terms.

    Commercial use. AGPL does not forbid it; forbidding it would make this project "source-available" rather than open source, because the Open Source Definition requires that a licence place no restriction on fields of endeavour. What AGPL does forbid is commercial use that keeps the modifications closed. For a company that means publishing every change it makes under AGPL on the same server it runs, which is why in practice nobody takes this code and builds a closed product on it.

    If you need to do that - keep modifications private, or ship Fluxy inside a closed product - a separate commercial licence is the only route. Open an issue at https://github.com/fluxy-rs/fluxy/issues and describe the use.

    Contributions. Bug fixes and features belong here rather than in a fork: a pull request against main is easier to review, easier to carry forward, and keeps everyone on one implementation. See AGENTS.md and AGENTS.md for the shape a contribution is expected to take.