Skip to content

feat: discover and manage LAN and tailnet hosts - #32

Draft
nixfred wants to merge 18 commits into
thisisgm:mainfrom
nixfred:feat/network-productivity
Draft

feat: discover and manage LAN and tailnet hosts#32
nixfred wants to merge 18 commits into
thisisgm:mainfrom
nixfred:feat/network-productivity

Conversation

@nixfred

@nixfred nixfred commented Sep 3, 2026

Copy link
Copy Markdown

Dependency order

The branch contains those exact commits so it is testable now; GitHub's diff will shrink as the prerequisite PRs merge.

What changes

  • discovers Tailscale peers without treating Tailscale as a filesystem; file access still uses SFTP/SMB/NFS/WebDAV
  • discovers bounded Avahi SSH, SMB, NFS, WebDAV, and WebDAVS services on the LAN
  • adds Quick Connect for user@host:/path, host/share, bare hosts, supported URIs, IPv4, and bracketed IPv6
  • persists eight sanitized successful recents plus separate Wake profiles
  • exposes unknown, online, offline, connecting, mounted, and failed health states
  • adds reconnect, unmount, copy address, SSH terminal, Taildrop, edit/remove, and Wake-on-LAN actions by capability
  • adds native flea --wake <mac> validation and a 102-byte UDP magic packet
  • bounds every discovery/listing child and keeps LAN/GIO usable across all Tailscale failure states

Additional bugs fixed

  • IPv6 hosts were split as ports; copy-address also lost a bracket
  • custom SSH ports were rejected
  • a stale Wake profile survived edits or clearing the MAC
  • discovered mounted rows offered Unmount but could not execute it
  • clipboard/Wake reported success before child exit
  • older recents shadowed fresh discovery metadata
  • delayed unmount cleared its identity before health cleanup
  • bare-server gio list could hang forever
  • Quick Connect could persist query tokens or fragments
  • mixed or same-mount transfers could be mislabeled as crossing remote hosts
  • a refused transfer could leak its classification into the next operation
  • a quiet process run could reuse an earlier mount, share, device, or Taildrop response
  • New Folder created a directory on rclone but its automatic rename failed with EINVAL
  • Network places: edit, remove, and live SFTP matching #21's newer @-in-path and failed-live-remove fixes are retained

Verification, repeated twice

  • clean debug and release builds, zero warnings
  • 345 Rust tests in debug and 345 in release
  • ./tests/run-all.sh — 13 suites, 0 failed; final pushed head has 1,270 JS/QML checks
  • real ~/google rclone rename succeeds; occupied destination remains untouched
  • stale-output regression mutation: removing one of the six resets makes the new suite fail
  • process-boundary cases for missing/empty/malformed/failed/timed-out discovery, wedged GIO, argv integrity, action exits, and secret-free persistence
  • ./tools/flea-qmllint-gate — 0 regressions
  • ./tools/flea-file-budget — all hard caps pass
  • git diff --check
  • two live Wayland loads reached Configuration Loaded
  • pointer-driven tests/ui.sh cases: omarchy-drive is not installed, packaged, or publicly discoverable under that name on this machine; deterministic QML process tests and live loads are green

Safety

Discovery and recovery never run sudo, start daemons, or change authentication. External commands are argv-direct; persisted URIs strip passwords, query strings, fragments, and control characters. rclone compatibility is limited to directories on mounts identified exactly as fuse.rclone; files and all other filesystems retain atomic no-clobber rename.

TomFaulkner and others added 13 commits September 3, 2026 16:09
r still only relabels. Edit opens the add-location form over the
bookmark line so a bad URI can be rewritten in place.

An unmounted share's menu is Edit alone, which is how a bookmark
gio cannot mount (sftp://user@host:22/~; gio does not expand ~)
gets fixed. A mounted share keeps Unmount first, and Ctrl+E reads
releaseAction rather than the first menu row so it cannot fire Edit.

Places.replace rewrites the matched GTK bookmarks line; an empty
old URI appends, which is also the add dialog's write now.
Edit rewrites a bookmark; there was still no way to drop one.
Remove deletes the GTK bookmarks line, and unmounts if it was live
so the row leaves the rail instead of becoming a mount-only leftover.
The add form writes sftp://user@host:22/path and gio mount -l reports
the same share without the default port. normalize only stripped
trailing slashes, so opening a saved place added a second, brighter
row and left the cursor on the dim bookmark.

Drop a scheme's default port in the same canonical form already used
on write and on dedup. A non-default port is kept.
After port-collapse, gio's row won the rail and kept gio's own name
("tom" for an SFTP session). Rename wrote the bookmarks file, then the
poll put "tom" back. Places.networkEntries prefers the bookmark label.

Unmount of a share Flea is listing also failed as busy; go Home first
when the open FUSE path is that share, then gio mount -u.
gvfsd-sftp mounts one connection per host. gio mount -l lists
sftp://user@host/ even when the bookmark is .../home/tom, so the
home row stayed dim and Unmount of the root failed as busy while
Flea was listing the home path on the same FUSE mount.

A second SFTP path on that host stays its own row and reads as
mounted. Unmount sends gio the live root URI and leaves Home when
any path on that connection is open. SMB/NFS stay per-share.
Protocols.parse split credentials on the last @ in the whole URI, so a
path like inbox@2026 was stolen as a username on save. The path is now
cut off before that search.

Remove of a live share wrote the bookmarks file before gio mount -u
finished, so a busy unmount left the share mounted with no row to retry
from. The file is rewritten from unmountProcess.onExited only when gio
succeeds; an idle bookmark is still dropped immediately.
Detect copies and moves whose source and destination are separate GVFS mounts, label them explicitly, and prove they retain the existing backend's copy, move, and undo semantics.

LR-T: ./tests/run-all.sh (10 suites, 0 failed); ./tests/ops.sh; ./tools/flea-qmllint-gate; ./tools/flea-file-budget

LR-D: remote transfers already worked but were indistinguishable from local operations in progress feedback

LR-K: ui/js/Remote.js; ui/js/Ops.js; tests/ops.sh
Turn share and menu rows into bounded accelerated views, keep keyboard cursors visible, and add deterministic wheel arithmetic and surface-coverage tests.

LR-T: ./tests/run-all.sh (11 suites, 0 failed); ./tools/flea-qmllint-gate; ./tools/flea-file-budget

LR-D: large SMB and Taildrop lists overflowed their viewport; pointer scrolling had no regression coverage

LR-K: ui/ContextMenu.qml; ui/ShareBrowser.qml; tests/scroll.sh
Add bounded parsers for Avahi and Tailscale, Quick Connect shorthand, sanitized recent/Wake state, IPv6-safe protocol parsing, and metadata-aware rail merging.

LR-T: ./tests/js.sh (1,263 checks, 0 failed)

LR-D: discovery, shorthand, history, and live mount state need one canonical URI and first-position metadata merge

LR-K: ui/js/Discovery.js; ui/js/Tailnet.js; ui/js/QuickConnect.js; ui/js/Recents.js
Wire LAN and tailnet discovery, health, recents, Quick Connect, SSH/Taildrop/clipboard actions, and validated Wake-on-LAN through one composed network rail.

LR-T: cargo test (341 passed); ./tests/modes.sh; live Wayland Configuration Loaded

LR-D: Tailscale supplies private reachability while GIO protocols remain the filesystem transport

LR-K: ui/NetworkRail.qml; ui/NetworkDiscovery.qml; ui/NetworkActions.qml; src/wol.rs
Cover real Quickshell process calls, timeout and malformed states, action exit reporting, secret-free persistence, packaging dependencies, and operator-facing network documentation.

LR-T: ./tests/run-all.sh (12 suites, 0 failed); ./tests/network-services.sh

LR-D: command-backed discovery and actions require failure, empty, malformed, unavailable, and timeout coverage

LR-K: tests/network-services.sh; tests/network-services.qml; README.md; PKGBUILD
Remove separator-only lines after combining PR thisisgm#21's safe-remove path with discovery health; behavior is unchanged.

LR-T: ./tests/budget.sh

LR-D: the integrated service reached 405 lines against the repository's 400-line hard limit

LR-K: ui/NetworkMounts.qml
@coderabbitai

coderabbitai Bot commented Sep 3, 2026

Copy link
Copy Markdown

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Compare GVFS mount roots and require every source to be remote and different from the destination before saying a batch crosses remote hosts.

LR-T: ./tests/js.sh (1,171 checks, 0 failed); ./tests/ops.sh

LR-D: mixed batches and same-mount copies were described too broadly

LR-K: ui/js/Remote.js; tests/js/remote.js
Reset a pending remote kind when the backend refuses a transfer before transferstarted, fail the backend integration test when the remote move never finishes, and cover move wording.

LR-T: ./tests/js.sh (1,173 checks, 0 failed); ./tests/ops.sh; ./tools/flea-qmllint-gate; ./tools/flea-file-budget

LR-D: a pre-start error could leak remote wording into the next transfer

LR-K: ui/PaneWire.qml; ui/js/Ops.js; tests/ops.sh
Fall back from unsupported RENAME_NOREPLACE only for directories on fuse.rclone. Preserve no-clobber behavior with an explicit collision check and rclone's directory-move contract, while keeping files and other filesystems on the atomic path.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants