# ExNetfs
Elixir bindings to macOS **NetFS.framework** for mounting and unmounting network
file systems (SMB, NFS, AFP, WebDAV).
NetFS is the framework Finder uses under the hood for "Connect to Server…". It
handles protocol negotiation, Kerberos authentication, DFS referral resolution,
and the actual mount syscall.
## Why NetFS over `mount_smbfs`?
- **Kerberos-aware**: Automatically uses TGT from credential cache (populated by `ExKrb5.kinit/2`)
- **DFS support**: Follows SMB DFS referrals automatically
- **Proper error codes**: Returns structured errors instead of exit codes
- **Mount point reporting**: Returns the actual path where the share was mounted
- **No shell-out**: Direct framework call, no `System.cmd` needed
## Installation
```elixir
def deps do
[{:ex_netfs, "~> 0.1.0"}]
end
```
**Requirements**: macOS only. Links against `NetFS.framework` and `CoreFoundation.framework`.
## Usage
### Mount with Kerberos (AD-joined Mac)
```elixir
# First, get a Kerberos ticket
:ok = ExKrb5.kinit("user@AD.EXAMPLE.COM", password)
# Mount — NetFS picks up the TGT automatically
{:ok, ["/Volumes/share"]} = ExNetfs.mount("smb://fileserver.ad.example.com/share")
```
### Mount with explicit credentials
```elixir
{:ok, [mountpoint]} = ExNetfs.mount("smb://nas.local/backup",
user: "admin",
password: "secret")
```
### Mount hidden at specific path
```elixir
{:ok, _} = ExNetfs.mount_hidden("smb://server/share", "/Volumes/.myshare")
```
### Mount as guest
```elixir
{:ok, [path]} = ExNetfs.mount_guest("smb://publicserver/public")
```
### Unmount
```elixir
:ok = ExNetfs.unmount("/Volumes/share")
:ok = ExNetfs.force_unmount("/Volumes/stuck_share")
```
### List network mounts
```elixir
ExNetfs.list_mounts()
# => [{"//server/share", "/Volumes/share", "smbfs"},
# {"nfsserver:/export", "/Volumes/export", "nfs"}]
```
### Check if mounted
```elixir
ExNetfs.mounted?("/Volumes/share") # => true
```
### Find mount by server/share
```elixir
ExNetfs.find_mount("fileserver", "share")
# => {:ok, "/Volumes/share"}
```
## Mount Options
### Open Options (session-level)
| Atom | Effect |
|------|--------|
| `:no_ui` | Suppress authentication dialogs |
| `:guest` | Login as guest user |
| `:allow_loopback` | Allow mounting from localhost |
### Mount Options (filesystem-level)
| Atom | Effect |
|------|--------|
| `:no_browse` | Hide from Finder sidebar (MNT_DONTBROWSE) |
| `:read_only` | Mount read-only (MNT_RDONLY) |
| `:allow_sub_mounts` | Allow mounting subdirectories of share |
| `:soft_mount` | Soft failure semantics (shorter timeout) |
| `:mount_at_dir` | Mount exactly at mountpath, not below it |
## Full AD Auto-Mount Example
```elixir
# 1. Authenticate to AD
:ok = ExKrb5.kinit("mac.w@AD.APTALASKA.COM", password)
# 2. Look up user's home share from AD
{:ok, node} = ExOpenDirectory.connect(:search)
{:ok, record} = ExOpenDirectory.find_user(node, "mac.w")
{:ok, attrs} = ExOpenDirectory.get_attributes(record, ["dsAttrTypeStandard:SMBHome"])
# => {:ok, [{"dsAttrTypeStandard:SMBHome", ["\\\\server\\share"]}]}
# 3. Convert UNC path to SMB URL
[{_, [unc_path]}] = attrs
smb_url = unc_path |> String.replace("\\", "/") |> then(&"smb:#{&1}")
# 4. Mount silently
{:ok, [mountpoint]} = ExNetfs.mount_smb_kerberos(smb_url)
```
## License
MIT