This file explains how macfuseGui is wired today, using the current code paths.
macfuseGui is a menu-bar-only macOS app (LSUIElement=true).
Main entry points:
macfuseGui/App/macfuseGuiApp.swiftmacfuseGui/App/AppDelegate.swiftmacfuseGui/App/AppEnvironment.swift
Main orchestration layer:
macfuseGui/ViewModels/RemotesViewModel.swift
Main service layers:
- Mount stack:
macfuseGui/Services/MountManager.swiftmacfuseGui/Services/UnmountService.swiftmacfuseGui/Services/ProcessRunner.swift
- Browser stack:
macfuseGui/Services/RemoteDirectoryBrowserService.swiftmacfuseGui/Services/Browser/RemoteBrowserSessionManager.swiftmacfuseGui/Services/Browser/LibSSH2SessionActor.swiftmacfuseGui/Services/Browser/LibSSH2SFTPTransport.swiftmacfuseGui/Services/Browser/LibSSH2Bridge.c
AppDelegate.applicationDidFinishLaunching does four critical things before normal app setup:
- Skips full startup when running under XCTest host.
- Acquires singleton lock file (
/tmp/com.visualweb.macfusegui.instance.lock) withflock. - Detects duplicate app instances (bundle ID, executable name, localized name) and activates old instance.
- Terminates duplicate current process when needed.
Why this exists:
- Prevent duplicate status bar icons.
- Prevent test-host processes from creating real menu UI or recovery timers.
RemotesViewModel owns user-facing runtime state:
remotes(saved configs)statuses(live connection state)desiredConnections(user intent for recovery)- recovery timers/tasks
- operation supervision table (
remoteOperations)
Rule:
- All remote lifecycle decisions flow through
RemotesViewModel.
The app uses per-remote supervision.
One active operation per remote ID:
connectdisconnectrefreshtestConnection
Cross-remote concurrency:
- Allowed in parallel.
- Bounded by
OperationLimiter(maxConcurrent: 4). - Waiting in the limiter is cancellation-aware: a superseded operation leaves the queue immediately instead of delaying the ones behind it.
Conflict policy:
- Manual actions use
latestIntentWins. - Recovery refresh paths use
skipIfBusy.
Watchdogs:
- Each operation has watchdog timeout handling.
- Timed-out operations are cancelled and logged with operation ID.
RemotesViewModel.performConnect:
- Validates mount-point uniqueness.
- Sets status
connecting. - Resolves password from Keychain (password mode).
- Calls
MountManager.connectwith timeout guard. - Applies final state (
connectedorerror).
MountCommandBuilder turns a RemoteConfig into sshfs arguments:
- Auth mode:
SSH Private Key→IdentityFile=...System SSH→ no key; OpenSSH agent/config decidesPassword→ auth pinned insidessh_command=/usr/bin/ssh -o ...
- Optional
ProxyJump. - Cache mode:
nolocalcachesby default- timed metadata caches when freshness is turned off, using
sshfs -hcapability detection
- The source path always ends in
/, so symlinked remote directories mount their target.
RemotesViewModel.performDisconnect:
- Removes remote from
desiredConnections. - Sets status
disconnecting. - Calls
MountManager.disconnect. - On timeout, calls
forceStopProcessesthenrefreshStatus. - Applies final state (
disconnectedorerror).
MountManager.refreshStatus performs anti-flap checks:
- mount table probe (
mountparsing) - responsiveness probe (
stat) - fallback
dfprobe - brief retry before downgrade
If the mount is missing from the mount table but the path still responds, connected is kept for up to 4 consecutive misses before the remote is marked error for cleanup and reconnect. A cancelled refresh returns the last cached status rather than an error.
This prevents false dropouts and reconnect storms from single probe misses.
Mount path escape decoding:
MountStateParser.decodeEscapedMountFieldis the canonical decoder for macOSmountanddf -Pescape sequences.- Handles full octal range
\001–\377. BothMountManagerandUnmountServiceuse it for df-based path comparisons.
MountManager is an actor. Actor safety alone does not prove there is no practical bottleneck.
To make behavior measurable, logs include:
mount call ... queuedAtMs ... opAgeMs ...(fromRemotesViewModelbefore await)actor enter op=... queueDelayMs=...(insideMountManager)- probe windows with
remoteIDandoperationID:mount-inspectdf-inspectmount-responsive-checksshfs-connect
This lets you prove overlap versus serialization from one log stream.
Recovery acts on desiredConnections only.
Triggers:
- periodic timer (15s)
- wake notifications
- network restored notifications
- external unmount notifications
Burst retries:
- wake:
0s, 1s, 3s, 8s - network restore:
0s, 2s, 6s
Before the bursts:
- Wake runs one deduplicated preflight cleanup (parallel fast force-unmount of desired remotes). External-unmount events are ignored while it runs.
- Network loss cancels pending reconnects and runs a fast cleanup after a 0.5s debounce.
- Network restore waits 1.5s, then runs any deferred startup auto-connect before its burst.
Periodic deep checks are skipped when all desired remotes are stable.
Timing thresholds (watchdogs, periodic intervals, unmount caps, browser circuit breaker) are centralized in RuntimeConfiguration in macfuseGui/App/AppEnvironment.swift.
Browser sessions are separate from mount lifecycle.
Flow:
- UI opens browser sheet.
RemoteBrowserViewModelopens a session viaRemoteDirectoryBrowserService.LibSSH2SessionActorhandles retries, health, sticky cache.LibSSH2SFTPTransporttalks to native C bridge (LibSSH2Bridge.c).
Reliability contract:
- stale cache is shown during reconnect windows
- empty folder is confirmation-checked before treated as true empty
- stale request responses are dropped by monotonic request ID
Security and auth:
- After the SSH handshake and before authentication, the C bridge checks the host key against
~/.ssh/known_hosts. Unknown hosts are trusted on first use and recorded; a mismatch is a hard failure. - Only
PasswordandSSH Private Keyauth are supported.System SSHandProxyJumpremotes cannot use the browser yet.
Config store:
~/Library/Application Support/macfuseGui/remotes.jsonRemoteStoreis@MainActor
Secrets:
- Keychain only (
com.visualweb.macfusegui.password) KeychainService.readPasswordtrims leading/trailing whitespace before returning and returnsnilfor whitespace-only values. This prevents silent SSH auth failures from clipboard-pasted trailing newlines without modifying what is stored in Keychain.
Security rules:
- no plaintext password persistence
- no shell interpolation
- command execution via
Processargument arrays - diagnostics are redacted
Quit path is intentionally forceful to avoid hung app states:
- Post
.forceQuitRequested. - Close sheets/modals/windows aggressively.
- Await
prepareForTerminationinRemotesViewModel. - Call
NSApp.terminate. - Fallback
_exit(0)timer if app does not exit.
Run from repo root:
ARCH_OVERRIDE=arm64 ./scripts/build.sh
scripts/audit_mount_calls.py && xcodebuild -project macfuseGui.xcodeproj -scheme macfuseGui -configuration Debug -derivedDataPath build/DerivedData -destination 'platform=macOS,arch=arm64' test CODE_SIGNING_ALLOWED=NOPurpose:
- Ensures
MountManagercallsites explicitly forwardoperationID. - Runs test suite for concurrency, timeout, and recovery regressions.
See COMMIT_WORKFLOW.md for commit message format and changelog rules before pushing.