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?

Installation

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)

# 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

{:ok, [mountpoint]} = ExNetfs.mount("smb://nas.local/backup",
  user: "admin",
  password: "secret")

Mount hidden at specific path

{:ok, _} = ExNetfs.mount_hidden("smb://server/share", "/Volumes/.myshare")

Mount as guest

{:ok, [path]} = ExNetfs.mount_guest("smb://publicserver/public")

Unmount

:ok = ExNetfs.unmount("/Volumes/share")
:ok = ExNetfs.force_unmount("/Volumes/stuck_share")

List network mounts

ExNetfs.list_mounts()
# => [{"//server/share", "/Volumes/share", "smbfs"},
#     {"nfsserver:/export", "/Volumes/export", "nfs"}]

Check if mounted

ExNetfs.mounted?("/Volumes/share")  # => true

Find mount by server/share

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

# 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