Add fstab-backed custom mount point support for volumes#50
Add fstab-backed custom mount point support for volumes#50DrapNard wants to merge 2 commits intohomielab:mainfrom
Conversation
There was a problem hiding this comment.
Pull request overview
Adds per-volume custom mount point support that’s persisted by stable volume identity and applied via /etc/fstab, alongside a menu bar popover UI flow for managing mounts.
Changes:
- Adds an inline “Custom Mount Point” editor to each volume row and persists mount point + optional bookmark metadata.
- Applies custom mount points by writing managed entries into
/etc/fstab(and removing them when cleared). - Reworks menu bar presentation to an app-owned
NSStatusItem+NSPopover, and adds shell quoting helpers for safer command construction.
Reviewed changes
Copilot reviewed 14 out of 14 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| MountMate/Views/Main/MainView.swift | Adds global editor state + inline custom mount point editor UI and save flow. |
| MountMate/Views/Components/PopoverContent.swift | Adjusts popover container sizing behavior (removes fixedSize). |
| MountMate/Utilities/Shell.swift | Adds String helpers for shell-quoting and AppleScript string literal escaping. |
| MountMate/Utilities/AppDelegate.swift | Implements status item + popover lifecycle, sizing, and outside-click closing logic. |
| MountMate/Resources/en.lproj/Localizable.strings | Adds localized strings for the custom mount point feature. |
| MountMate/Resources/zh-Hans.lproj/Localizable.strings | Adds localized strings for the custom mount point feature. |
| MountMate/Resources/zh-Hant.lproj/Localizable.strings | Adds localized strings for the custom mount point feature. |
| MountMate/Resources/vi.lproj/Localizable.strings | Adds localized strings for the custom mount point feature. |
| MountMate/Resources/uk.lproj/Localizable.strings | Adds localized strings for the custom mount point feature. |
| MountMate/Models/ManagedVolumeInfo.swift | Introduces VolumeCustomMountPoint model for persisted settings. |
| MountMate/Managers/PersistenceManager.swift | Adds persistence + /etc/fstab read/modify/install logic for custom mount points. |
| MountMate/Managers/DriveManager.swift | Adds remount + mount point validation/inspection helpers and improves shell quoting for mount/unmount. |
| MountMate/App/MountMateApp.swift | Removes MenuBarExtra-based UI; relies on AppDelegate-owned status item/popover. |
| MountMate.xcodeproj/project.pbxproj | Updates signing team ID in project settings. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| let selectedFolderURL = | ||
| editorState.selectedFolderURL?.path == path ? editorState.selectedFolderURL : nil | ||
| if let error = persistence.applyCustomMountPoint(path, selectedURL: selectedFolderURL, for: volume) | ||
| { | ||
| editorState.inlineError = error |
There was a problem hiding this comment.
applyCustomMountPoint(...) performs filesystem reads/writes and runs osascript for privilege escalation; calling it directly from the button action will block the SwiftUI main thread (and can appear frozen while the password prompt is pending). Consider moving this work onto a background task/queue and surfacing a saving state (disable buttons/spinner), then update editorState back on the main thread.
| let script = "do shell script \(command.appleScriptStringLiteral) with administrator privileges" | ||
| let result = runShell("/usr/bin/osascript -e \(script.shellQuoted)") | ||
| if let error = result.error, !error.isEmpty { | ||
| throw NSError( |
There was a problem hiding this comment.
This uses runShell(...) to execute an osascript ... with administrator privileges operation. runShell currently hard-times-out after 15 seconds, which is very likely to fail if the user takes longer than 15 seconds to respond to the admin password prompt (or if the prompt is slow to appear). Consider adding a configurable/longer timeout (or no timeout) for privileged operations and reporting cancellation separately from timeout.
| let trimmed = line.trimmingCharacters(in: .whitespaces) | ||
| guard !trimmed.isEmpty, !trimmed.hasPrefix("#") else { return false } | ||
| return trimmed.hasPrefix("UUID=\(volumeUUID) ") |
There was a problem hiding this comment.
Conflict detection only checks for the literal prefix "UUID=<uuid> " (single space). /etc/fstab fields are whitespace-separated and may use tabs or multiple spaces, so this can miss real conflicts and allow MountMate to write a duplicate entry. Consider parsing the line by whitespace (or using a regex for UUID=<uuid>\s) before deciding there is no conflict.
| let trimmed = line.trimmingCharacters(in: .whitespaces) | |
| guard !trimmed.isEmpty, !trimmed.hasPrefix("#") else { return false } | |
| return trimmed.hasPrefix("UUID=\(volumeUUID) ") | |
| let trimmed = line.trimmingCharacters(in: .whitespacesAndNewlines) | |
| guard !trimmed.isEmpty, !trimmed.hasPrefix("#") else { return false } | |
| let fields = trimmed.split(whereSeparator: \.isWhitespace) | |
| guard let firstField = fields.first else { return false } | |
| return firstField == "UUID=\(volumeUUID)" |
| COMBINE_HIDPI_IMAGES = YES; | ||
| CURRENT_PROJECT_VERSION = 21; | ||
| DEVELOPMENT_TEAM = 79LQ4MHVMG; | ||
| DEVELOPMENT_TEAM = 5TU767C42B; | ||
| ENABLE_HARDENED_RUNTIME = YES; |
There was a problem hiding this comment.
This PR changes the Xcode project DEVELOPMENT_TEAM setting. This is usually machine/account-specific and can break signing for other contributors/CI. Consider reverting these changes and managing signing via local xcconfig/user settings (or leaving DEVELOPMENT_TEAM unset in the shared project).
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Summary
diskutil mount -mountPointremounts to/etc/fstabupdates so normal mounts and plug-in auto-mount can use the custom pathDriveManagerto plaindiskutil mountand surfacing clear validation and system-rule errorsTesting
xcodebuild -project MountMate.xcodeproj -scheme MountMate -configuration Debug -derivedDataPath .codex-build/DerivedData -clonedSourcePackagesDirPath .codex-build/SourcePackages CODE_SIGNING_ALLOWED=NO CODE_SIGNING_REQUIRED=NO build