Skip to content

Instantly share code, notes, and snippets.

@rudelm
Last active March 18, 2024 06:56
Star You must be signed in to star a gist
Save rudelm/7bcc905ab748ab9879ea to your computer and use it in GitHub Desktop.
Use autofs on Mac OS X to mount network shares automatically during access

Autofs on Mac OS X

With autofs you can easily mount network volumes upon first access to the folder where you want to mount the volume. Autofs is available for many OS and is preinstalled on Mac OS X so I show you how I mounted my iTunes library folder using this method.

Prepare autofs to use a separate configuration file

autofs needs to be configured so that it knows where to gets its configuration. Edit the file /etc/auto_master and add the last line:

#
# Automounter master map
#
+auto_master		# Use directory service
/net			-hosts		-nobrowse,hidefromfinder,nosuid
/home			auto_home	-nobrowse,hidefromfinder
/Network/Servers	-fstab
/-			-static
/-          auto_smb    -nosuid,noowners
#/-			auto_afp	-nobrowse,nosuid

This will tell autofs to look for a file in the '/etc' folder with name 'auto_smb'. In this case I want to create a configuration for automatically mount SMB volumes. You are free to choose a different name and can also use afp/cifs/nfs etc.

Be aware that macOS updates can overwrite this file! Make sure you'll check the content of this file after you've updated. I've encountered this behaviour with the latest macOS Catalina 10.15.7 supplemental update.

Content of the configuration file

Normally Mac OS X tries to mount network shares into the '/Volumes' folder. This is the default folder for all mounted shares on a mac. However, if you try to directly mount into this folder, autofs will fail. You just add a '/../' in front of your desired mount path and Mac OS X will even accept the Volumes folder. However, some Mac OS Version doesn't like this so I switched over to use my own folder named '/mount'.

If you want to configure AFP, do it like this:

So add this line to /etc/auto_afp:

/../Volumes/music	-fstype=afp,rw afp://ip-address:/music

Mac OS X is clever enough to lookup the username and password from the Mac keychain so there's no need to add the username and password in clear text to the configuration file.

If you want to configure SMB, do it like this:

Add this line to /etc/auto_smb:

/mount/music    -fstype=smbfs,soft,noowners,nosuid,rw ://username:password@ip-address:/music

Unfortunately you will need to add the user and password to the resource :( You can try to lock it down further using the Mac OS permissions but that won't help when the attackers user got admin rights as well.

If you're using macOS Catalina 10.15 and macOS Big Sur 11

You’ll just have to prepend your existing automount paths with /System/Volumes/Data. This is because macOS creates a second APFS volume for your user data, whereby the existing system installation is moved to a read-only APFS volume.

This is the version of /etc/auto_smb in Catalina:

/System/Volumes/Data/mount/music    -fstype=smbfs,soft,noowners,nosuid,rw ://username:password@ip-address:/music

Modern way using synthetic.conf

Thanks to rjc I now know about synthetic.conf. From the man page:

synthetic.conf describes virtual symbolic links and empty directories to be
created at the root mount point.  Because the root mount point is read-only
as of macOS 10.15, physical files may not be created at this location.  All
writeable paths must reside on the data volume, which is mounted at
/System/Volumes/Data.

synthetic.conf provides a mechanism for some limited, user-controlled file-
creation at /.  The synthetic entities described in this file are
synthesized by the kernel during early system boot.  They are not
physically present on the disk, but when the system is booted, they behave
as if they were within certain parameters.

which sounds exactly what we want. Create a new file /etc/synthetic.conf. If it does not exist, create the file with this content:

# create a symbolic link named "music" at / which points to
# "System/Volumes/Data/mount/music", a writeable location at the root of the data volume
music   System/Volumes/Data/mount/music

The first column describes the symbolic link at /. The next column is separated with a tab and describes the location under / which should be linked to the first column. You can also leave the second column empty. This would create an empty folder in which we could automount again. If you want the minumum amount of changes, use example from above. After a reboot the folder should be available at the root of your macOS installation.

Restoring autofs after macOS updates

The recent macOS updates seem to overwrite the /etc/auto_master file. You can try to set the file to read-only using the extended attributes of macOS. For more details see this blog post. Please add the entry for your autofs file again to the auto_master file and save the file. We'll try to set the auto_master file to read-only, in hope it won't be overwritten again.

~ ❯ ls -lO /etc/auto_master                                         at 20:55:44
-rw-r--r--  1 root  wheel  - 226 May  6 20:49 /etc/auto_master
~ ❯ sudo chflags schg /etc/auto_master                        ✘ INT at 21:01:04
~ ❯ ls -lO /etc/auto_master                                         at 21:01:10
-rw-r--r--  1 root  wheel  schg 226 May  6 20:49 /etc/auto_master

The file is now write protected. You can try to edit it again with sudo and it will be read only. Its the same as when you set the file write protected in the finder.

if you use

sudo chflags noschg /etc/auto_master 

it will be writeable again.

Access the folder and see autofs in action

You now need to restart the autofs service with the command 'sudo automount -cv'. If you now type mount, you'll see a listing of currently mounted volumes. Your desired volume shouldn't be mounted, so unmount it with 'sudo umount /Volumes/volumename' or 'sudo umount /mount/music' before we continue.

You can now switch to '/Volumes/music' or '/mount/music' folder or let it list on the terminal. If you're using macOS Catalina you can open /System/Volumes/Data/mount/music. Once you do that autofs will automatically try to mount the desired volume into this folder.

See an example and explanation in action

Visit my blog post where I explain this gist a little bit more in detail. A complete list of blog posts with autofs can be found here.

@msigler-eb
Copy link

MacOS v11.3.1

OS updates from Apple keep overwriting auto_master thus removing the line to my auto_shares file to configure my SMB shares.

Any ideas on how to make these changes retain across updates?

@gabeosx
Copy link

gabeosx commented May 6, 2021

MacOS v11.3.1

OS updates from Apple keep overwriting auto_master thus removing the line to my auto_shares file to configure my SMB shares.

Any ideas on how to make these changes retain across updates?

It was removed from mine as well, but the volumes still mounted... weird

@rudelm
Copy link
Author

rudelm commented May 6, 2021

MacOS v11.3.1

OS updates from Apple keep overwriting auto_master thus removing the line to my auto_shares file to configure my SMB shares.

Any ideas on how to make these changes retain across updates?

oh that's a great reminder! I've checked my config and it was also removed again. I've mentioned the necessary steps to make it work again here: https://centurio.net/2020/11/11/automount-not-working-after-macos-catalina-updates/

Since this is a file that is apparently writeable, I would try to set the file to being write protected:

~ ❯ ls -lO /etc/auto_master                                         at 20:55:44
-rw-r--r--  1 root  wheel  - 226 May  6 20:49 /etc/auto_master
~ ❯ sudo chflags schg /etc/auto_master                        ✘ INT at 21:01:04
~ ❯ ls -lO /etc/auto_master                                         at 21:01:10
-rw-r--r--  1 root  wheel  schg 226 May  6 20:49 /etc/auto_master

The file is now write protected. You can try to edit it again with sudo and it will be read only. Its the same as when you set the file write protected in the finder. See https://www.cyberciti.biz/faq/apple-osx-write-protecting-file-folders-bash-command/ for more details on setting the write protection.

if you use

sudo chflags noschg /etc/auto_master 

it will be writeable again. Maybe this will help in future updates.

@abrbon
Copy link

abrbon commented Sep 18, 2021

Hi,

Only issues i have now is that in the Finder it doesn't show the available free space and the capacity.
It just shows for example: 215 item, -- available

But when i mount it manually with the SHIFT+K (Connect to server) it does show the available space
Any one else has this issue?

This is since MacOS Big Sur btw

@rudelm
Copy link
Author

rudelm commented Sep 21, 2021

My guess is that the finder cannot differentiate between the volume's free disk space and the space available caused by the automounted folder.

@mfds
Copy link

mfds commented Dec 24, 2021

Here I am, back again to apply the content of this useful gist because Apple always reverts my auto_master after every update :(

@winkee01
Copy link

winkee01 commented Jan 3, 2022

the group is wheel, which doesn't allow write permission for users not in this group, how can I have write permission on the mounted folder on macOS?

@rjc
Copy link

rjc commented Jan 25, 2022

Unfortunately you will need to add the user and password to the resource :( You can try to lock it down further using the Mac OS permissions but that won't help when the attackers user got admin rights as well.

You don't have to - you can either use a system-wide nsmb.conf(5) config file for it or, better still, add the password to system keychain.

You’ll just have to prepend your existing automount paths with /System/Volumes/Data. This is because macOS creates a second APFS volume for your user data, whereby the existing system installation is moved to a read-only APFS volume.

You don't have to - that's what synthetic.conf(5) is for.

@rudelm
Copy link
Author

rudelm commented Jan 25, 2022

Hi @rjc thanks for your input! My main problem with adding credentials to either /etc/auto_smb or nsmb.conf is that it will be still readable by anybody with admin rights. Adding it to the keychain would be the best way (assuming it will be only accessible to the user using the mount in the login keychain). However, I've only found one possible solution which involves an additional launch daemon. That will open a share on startup but not upon access to the folder, like automount does. If you have an idea how we could point autofs to use the keychain entry, that would be awesome!

I did not know about synthetic.conf but it looks awesome! Maybe I'll try to update the GIST accordingly when I've got time. Thanks!

@apwelsh
Copy link

apwelsh commented Jan 25, 2022

Another issue is that is the Mac is a multi-user Mac, all concurrent users are sharing one login to access that share. NFS allows each user to access the share as themselves but the SMB mounts are as the mounting service account only. So for SMB there really needs to be a local mount to the user’s mount points, not the system ones, using the more Unix solution of mounting to mount folders instead. Unless allowing every user of the computer to access the network resources as the same user is acceptable.

@rudelm
Copy link
Author

rudelm commented Jan 25, 2022

yeah that's right. Please keep in mind, that I've used autofs to only move my iTunes library to a personal network share intended for my own mac. This was never intended for multi user Macs. Guess I'll really have to check the part about the final mount point location again. After all this solution worked now for almost 6 years and stuff has changed and evolved. If we'll figure out a proper multi user solution with keychain support, everyone will be happy :)

@rjc
Copy link

rjc commented Jan 25, 2022

@rudelm No worries! :^)

My main problem with adding credentials to either /etc/auto_smb or nsmb.conf is that it will be still readable by anybody with admin rights.

Anybody with admin right will be able to do anything on the system - that's expected.

SMB being stateful protocol, you either have to authenticate during mount or you save your credentials somewhere - otherwise, you can't do a real automount, there's no two way about it. However, what you do not want is any user to be able to see your credentials so you can either make sure that /etc/nsmb.conf is only readable by local admins or you stick it into the system keychain.

synthetic.conf(5) is the standard and correct way to do it now - there's no reason to link to any external articles, especially bad ones - in that particular one, the author complains about TAB characters where the manual page clearly states under FORMAT:

 synthetic.conf specifies a single synthetic entity per line. Each line may have one or two columns, separated by a tab character.

all you need is:

 man 5 synthetic.conf

:^)

@seamusdemora
Copy link

automount generally means that the block device is only mounted when it is needed/called for.

I need a mount point that I can put in a bash script that uses rsync to back up files on my Mac to a Synology drive. The script must run whether I'm logged in or not. Will this "recipe" support that?

@apwelsh
Copy link

apwelsh commented Jun 4, 2022

That doesn't matter. The auto mounter service mounts on demand based on the user accessing the resource. Your scheduled job must run as a user. That user, when it access the share, is a logged in user. You may not have a desktop, but that is not the requirement for the auto mounter.

@seamusdemora
Copy link

seamusdemora commented Jun 4, 2022

macOS Catalina 10.15.6

I've set /etc/auto_master iaw yr instructions, and tried this in /etc/auto_smb:

First this:

/Users/seamus/rsync_dest -fstype=smbfs,soft,noowners,nosuid,rw ://seamus:123456@192.168.1.102:/backups

Then this:

/System/Volumes/Data/mnt/rsync_dest -fstype=smbfs,soft,noowners,nosuid,rw ://seamus:123456@192.168.1.102:/backups

Then:

% sudo automount -cv     
automount: /System/Volumes/Data/home updated (/home -> /System/Volumes/Data/home)
automount: /System/Volumes/Data/mnt/rsync_dest mounted
automount: /Users/seamus/rsync_dest unmounted
% sudo umount /System/Volumes/Data/mnt/rsync_dest
% ls -l /System/Volumes/Data/mnt/rsync_dest
ls: rsync_dest: No locks available

My password is 8 characters with no special symbols (all lower-case letters & numbers)

I wonder if I've got my mount point set up properly... you said you created /mount - but where? Do you mean in the root filesystem???

Any ideas?

@jaques-sam
Copy link

jaques-sam commented Jun 4, 2022 via email

@rjc
Copy link

rjc commented Jun 6, 2022

Any ideas?

The examples look wrong - there should be no colon (:) before the the share part, unless it is NFS, and there should be no leading colon in there either.

Did you read synthetic.conf(5)?

@seamusdemora
Copy link

@rjc :

Did you read synthetic.conf(5)?

No - I followed the recipe; it never mentioned synthetic.conf AFAIK. The lines that were already in my /etc/auto_master were left as they were; I only added lines.

I tried this recipe several times, but never got it to work - I probably did something wrong. I used another recipe at https://useyourloaf.com/blog/using-the-mac-os-x-automounter/, and it worked for me. synthetic.conf was not my problem however... my first priority was getting this to work at all. Security improvements generally follow. I do appreciate the heads-up - I'll have a look at it now that I've got something working.

@rudelm
Copy link
Author

rudelm commented Jun 17, 2022

I've finally found the time to update the gist to include the synthetic.conf. What's still missing is the part where I can save the password in keychain instead of the auto_smb file.

@seamusdemora
Copy link

@rjc :

To follow up re synthetic.conf: I've read the manual. I've got to say that this may be the ugliest hack I've ever seen. Apple has painted themselves into a corner with their jazzy acronyms and unproven ideas (SIP, R-O filesystem), and this is their solution? The only good thing I can find to say about synthetic.conf is that it may be the only feature in Darwin that has a man page less than 10 years old.

But you seem to be an authority on this, and so I'd like to learn all I can. Can you provide a reference to any Apple-published documentation that explains the thinking behind this? Is there any Apple-published documentation on it? Working examples, etc??

@dcanadillas
Copy link

I am not able to make it work with AFP using the credentials from the Keychain. It works if I put the credentials in /etc/auto_afp:

/System/Volumes/Data/<mydir> -fstype=afp,rw afp://<username>:<password>@<ip_address>:/<my_volume>

But it doesn't work if I use credentials from Keychain (I mount the volume first in the Finder and check adding the credentials in the Keychain), and then configure automount without putting the credentials in the file afp://<ip_address>:/<my_volume>

This doc says that MacOS is clever enough to find the credentials in the Keychain, but it seems that my Monterey is not that clever :-)

Anyway, I have been trying other things that didn't work:

  • Copying the Keychain credentials from login items to system items
  • Mounting first the volume in the Finder, ejecting and then doing the sudo automount -vc
  • Deleting the credentials in Keychain and adding them by CLI:
    $ security add-internet-password -l <my_key_name> -a <username> -w <password> -D "network password" -r "afp " -s <ip_address> -T /System/Library/CoreServices/NetAuthAgent.app/Contents/MacOS/NetAuthSysAgent
    

I have also tried to use a mount point using synthetic.conf and didn't work either if not putting the credentials on the file. But of course I wasn't expecting this to make a difference regarding credentials.

The only way that I see that automount is able to find credentials from system is if a different folder of the volume is already mounted as a volume. But this is not a solution, because I want to use automount to be "automated" without any other previous external action. So, honestly, I don't see a very good way from security perspective having credentials in clear text in a file in /etc. It would be great to make the Keychain method work.

I finally ended to mount the volume as a login item from account preferences, but I think it would be better if auto_master worked with credentials from Keychain.

@rudelm
Copy link
Author

rudelm commented Aug 12, 2022

hi @dcanadillas that's also my experience. autofs doesn't use the items from Keychain, even when it would make sense. That's why I've started adding the username and password to the auto_ files.

AFP isn't the preferred protocol anymore and was replaced by SMB. I've seen some google hits for people having AFP working with automount and keychain, but always by mounting a volume manually beforehand or by using scripts.

I'm not sure if this is possible, but can't we let autoruns run scripts which will be placed instead of the hardcoded username and password? Ideally the same entry from Keychain, the same entry that Finder uses?

@macshome
Copy link

Typically autofs would expect kerberos for authentication.

@zonzorp
Copy link

zonzorp commented Feb 10, 2023

automount and indeed, NFS, still have their place and use. Almost 40 years ago when I worked for Sun, we pushed the message that the network is the computer, but there was no implication that people should pretend network or the computer were unimportant and only the UX mattered. I will not enable SMB on my NAS because I have Windoze on my network and you cannot in any reasonable way really secure that as long as people are allowed access to those Windoze machines. AFP? Never worked well. I gave up fighting AFP long ago. NFS is the only one that has been around and stable long enough to be considered. But Apple doesn't want to do NFS for whatever reason that only benefits Apple. As a result, their implementation is functional, but has unnecessary unpredictability, as opposed to SMB's unpredictability which appears to be organic. User credentials for file access is good. User credentials for share access was always A Bad Idea, IMNSHO. Automount is good, and Finder not knowing about it is just an intentional shortcoming on Apple's part. Finder does show NFS mounts like any other. It is only the automount ones that aren't usually set up for browsing with Finder before mounting. Apple's paranoid whacking of auto_master has been part of Apple's updates for years and years now. I still wish there was a configuration location for automount that survived updates, but the only answer I have found is to put my maps in one or more files, and always manually add back the line(s) that includes those maps after updates. I have resigned myself to the fact that it is simply a manual step in the update process, because Apple wants it that way.

@marfier
Copy link

marfier commented Feb 21, 2023

I recently ditched iCloud for a NAS. What I miss was the feature that automatically mounts the ~/Documents and ~/Desktop directory to iCloud Drive. Would this configuration allow me to replicate this for a NAS?

@L422Y
Copy link

L422Y commented Mar 8, 2023

feel free to credit ;) https://gist.github.com/L422Y/8697518

@cbitterfield
Copy link

I tried a number of variations. I can get synthentic.conf to work as its intended but I can't get automouters to work. AFP or SMB. The issue is around credentials. I can play games with root and sudo and get the mounts to mount but it is unreliable and mostly I get permissions errors

@permezel
Copy link

permezel commented Dec 7, 2023

I was in the process of abandoning my kluge and attempting to get something better, did some googs, and arrived here. Perhaps I'll keep my horrid hacks a bit more. FWIW, here is my kluge.

  1. Arrange things so that there is a /nas/ mount point. (see above for details)
  2. Create /nas/* as needed.
  3. Install passwords into keychain
  4. manually (too lazy to figure out how to auto run it) run a mounter script.
    % smb-mount mount --keep all >/dev/null 2>&1 &

The script below has to be edited to describe the assets one wants to mount. Too lazy to put it into separate config file.
The script also can be used to manage the keychain password entries.

I found that the --keep option was needed, or else the mounts would go stale over time.

I suppose I should put it in github or something, butI can never figure out how to appropriately package python projects.

I hereby release it free to whoever stumbles onto this gist.
Here is smb-mount:

#!/usr/bin/env python3
#
# Copyright (c) 2021 Damon Anton Permezel, all bugs revered.
#
# TODO:
#	add a `remount` option which will, if required, force unmount and re-mount.
#	-- `diskutil unmount force /nas/whatever`
#
import keyring
import click
import sys
import os
import subprocess
import threading
import pexpect
import logging
import time
import shutil

_pass_cmd = shutil.which('pass')
_user = os.getenv('USER')

# NAS resources
#   'example':		[ 'server',   'user',   'volume',  'mount-point', 'options' ],
_nas = {
    # for error testing
    'err':		[ 'nas.local', 'dap',   '/missing','/nas/err',	'-o nosuid,nodev,noatime,soft,-s' ],

    'dap':		[ 'nas.local', 'dap',   '/home',   '/nas/dap',   '-o nosuid,nodev,noatime,soft,-s' ],
    'nimda':		[ 'nas.local', 'nimda', '/home',   '/nas/nimda', '-o nosuid,nodev,noatime,soft,-s' ],

    'shared':		[ 'nas.local', 'dap', '/shared', '/nas/shared', '-o nosuid,nodev,noatime,soft,-s' ],
    'tax':		[ 'nas.local', 'dap', '/tax',	   '/nas/tax',	'-o nosuid,nodev,noatime,soft,-s' ],
    'music':		[ 'nas.local', 'dap', '/music', '/nas/music',	'-o nosuid,nodev,noatime,soft,-s' ],
    'video':		[ 'nas.local', 'dap', '/video', '/nas/video',	'-o nosuid,nodev,noatime,soft,-s' ],
    'nihongo-ro':	[ 'nas.local', 'dap', '/nihongo', '/nas/nihongo',	'-o rdonly,nosuid,nodev,noatime,soft,-s' ],
    'nihongo':		[ 'nas.local', 'dap', '/nihongo', '/nas/nihongo',	'-o nosuid,nodev,noatime,soft,-s' ],
}

_all = [ 'dap', 'nimda', 'shared', 'tax', 'music', 'video', 'nihongo' ]


@click.group()
def cli():
    pass

@cli.command()
@click.option('--server', prompt='Enter SMB server address', help='SMB server address', default='nas.local')
@click.option('--user', prompt='Enter SMB username', help='SMB user name', default=_user)
def auth_pass(server, user):
    """Establish authentication, password from `pass`.
    Create an authentication record in the keychain, per the `auth` subcommand.
    Pull the password from `pass show Auth/SMB/${server}/Users/${user}`.
    Will require (in my case) YubiKey interaction."""

    path = 'Auth/SMB/{}/Users/{}'.format(server, user)

    try:
        pwd = subprocess.run([_pass_cmd, 'show', path], capture_output=True)
    except:
        sys.exit(-1)
        pass

    if pwd.returncode != 0:
        print('Failed to find password entry for '+path)
        print(pwd.stderr.decode('UTF-8'), end='')
        sys.exit(pwd.returncode)
        pass

    pwd = pwd.stdout.decode('UTF-8')
    pwd = pwd.strip('\n')

    acnt = 'Auth/SMB/{}/{}'.format(server, user)
    serv = 'smb-pass'

    try:
        keyring.set_password(serv, acnt, pwd)
    except keyring.errors.PasswordSetError:
        print('Cannot set', serv, acnt)
        sys.exit(2)
        pass

    try:
        if keyring.get_password(serv, acnt) != pwd:
            print('keyring password mismatch')
            sys.exit(3)
            pass
    except keyring.errors.KeyringError as e:
        print('***keyring error:', e)
        sys.exit(4)
        pass

    sys.exit(0)
    pass

@cli.command()
@click.option('--server', prompt='Enter SMB server address', help='SMB server address', default='nas.local')
@click.option('--user', prompt='Enter SMB username', help='SMB user name', default=_user)
@click.password_option(help='SMB password')
def auth(server, user, password):
    """Establish the authentication.
    I keep the user passwords in `pass -c Auth/SMB/${server}/${user}`.
    However, this requires interaction with the YubiKey for each access.
    Accordingly, I cache them in the system keychain, presumably protected by
    the keychain unlock password (ie: login password)."""

    acnt = 'Auth/SMB/{}/{}'.format(server, user)
    serv = 'smb-pass'

    try:
        keyring.set_password(serv, acnt, password)
    except keyring.errors.PasswordSetError:
        print('Cannot set', serv, acnt)
        sys.exit(2)
        pass

    try:
        if keyring.get_password(serv, acnt) != password:
            print('keyring password mismatch')
            sys.exit(3)
            pass
    except keyring.errors.KeyringError as e:
        print('***keyring error:', e)
        sys.exit(4)
        pass

    sys.exit(0)
    pass

def get_pass(server, user):
    """Lookup password in keyring."""

    acnt = 'Auth/SMB/{}/{}'.format(server, user)
    serv = 'smb-pass'

    try:
        password = keyring.get_password(serv, acnt)
        if password is None:
            print('***no password entry for {}:{}'.format(serv, acnt))
            sys.exit(1)
            pass

        return password
        pass
    except keyring.errors.KeyringError as e:
        print('Looking up password for server:', server, 'user:', user)
        print('***keyring error:', e)
        sys.exit(1)
        pass

@cli.command()
@click.option('--server', prompt='Enter SMB server address', help='SMB server address', default='nas.local')
@click.option('--user', prompt='Enter SMB username', help='SMB user name', default=_user)
def check(server, user):
    """Verify authentication."""

    password = get_pass(server, user)

    # all I can do right now is just check the password entry exists
    sys.exit(0)
    pass

def is_mounted(path):
    """Check to see if `path` is a mount point for a remote volume."""

    try:
        if not os.path.isdir(path):
            os.makedirs(path)
            pass
    except Exception as e:
        print('*** Invalid path for os.makedirs(): ', path)
        sys.exit(1)
        pass
    pass

    try:
        s0 = os.lstat(path)
        s1 = os.lstat(path + '/..')
        return s0.st_dev != s1.st_dev
    except Exception as e:
        print('*** Invalid path for is_mounted(): ', path)
        sys.exit(1)
        pass
    pass


def try_mount(fs, keep=False):
    fmt = '%(asctime)s: %(message)s'
    logging.basicConfig(format=fmt, level=logging.INFO, datefmt='%H:%M:%S')

    if fs == 'all':
        threads = list()
        for fs in _all:
            t = threading.Thread(target=try_mount_one, args=(fs, keep,), daemon=True)
            t.start()
            threads.append(t)
            pass

        for i, t in enumerate(threads):
            t.join()
            pass

        sys.exit(0)
        pass
    else:
        t = threading.Thread(target=try_mount_one, args=(fs, keep,), daemon=True)
        t.start()
        t.join()
        sys.exit(0)
        pass
    pass

def try_mount_one(fs, keep=False):
    try:
        _try_mount_one(fs, keep)
    except Exception as e:
        name = threading.current_thread().name
        logging.info('%s: ****: fs:%s %r', name, fs, e)
        pass
    pass

def _try_mount_one(fs, keep):
    fs = str(fs)
    res = _nas[fs]
    usr = res[1]
    srv = res[0]
    vol = res[2]
    mnt = res[3]
    opt = res[4]
    # update = 'update' in opt
    update = '-u' in opt

    path = '//'+usr+'@'+srv+vol
    name = threading.current_thread().name

    # logging.info('%s: fs: %s', name, fs)

    while True:
        if not update and is_mounted(mnt):
            logging.info('%s: was mounted', fs)
            if keep != True:
                return
            time.sleep(60)
            continue

        # mount -t smbfs //username:userpass@myserver/PUBLIC /smb/public
        # print('/sbin/mount_smbfs '+opt+' '+path+' '+mnt)
        logging.info('/sbin/mount -t smbfs '+opt+' '+path+' '+mnt)
        child = pexpect.spawn('/sbin/mount -t smbfs '+opt+' '+path+' '+mnt, encoding='utf-8')
        # child = pexpect.spawn('/sbin/mount_smbfs '+opt+' '+path+' '+mnt, encoding='utf-8')
        try:
            child.expect('Password for .*:', timeout=120)
            # logging.info('Got password prompt')
            # print(child)
        except pexpect.exceptions.EOF as e:
            # logging.info('Got EOF')
            if 'error' in child.before:
                logging.error(child.before.strip('\n'))
                sys.exit(1)
            else:
                logging.info(child.before)
                time.sleep(60)
                continue
            pass
        except Exception as e:
            logging.info('%s: ***Did not get a password prompt from mount.', name)
            logging.info('%s: %r', name, e)
            continue

        pwd = get_pass(srv, usr)
        child.sendline(pwd)
        del pwd


        child.logfile_read = sys.stdout

        child.expect(pexpect.EOF, timeout=None)

        try:
            child.close()
        except Exception as e:
            loging.info('%s: ***close: %r', e)
            pass

        # poor attempt at purging the password info
        stat = child.exitstatus
        del child

        if keep != True:
            return stat

        if is_mounted(mnt) != True:
            logging.info('%s: ****: not mounted: ', name, mnt)
            return

        # clear update flag for loop
        update = False
        pass
    pass

@cli.command()
@click.argument('vol')
@click.option('--keep', is_flag=True, help='Keep monitoring / re-trying')
def mount(vol, keep):
    """Mount volume."""

    if vol == '?':
        # '?' will list the available volumes
        for m in _nas:
            click.echo(m)
            pass
        pass
    else:
        sys.exit(try_mount(vol, keep))
        pass

    sys.exit(0)
    pass

def try_unmount(fs):
    fmt = '%(asctime)s: %(message)s'
    logging.basicConfig(format=fmt, level=logging.INFO, datefmt='%H:%M:%S')

    if fs == 'all':
        for fs in _all:
            fs = str(fs)
            mnt = _nas[fs][3]
            res = subprocess.run(['/sbin/umount', mnt], capture_output=True)
            logging.info(res)
        pass
    elif fs in _all:
        fs = str(fs)
        mnt = _nas[fs][3]
        res = subprocess.run(['/sbin/umount', mnt], capture_output=True)
        logging.info(res)
        pass

    sys.exit(0)
    pass

@cli.command()
@click.argument('vol')
def unmount(vol):
    """Unmount volume."""

    if vol == '?':
        # '?' will list the available volumes
        for m in _nas:
            click.echo(m)
            pass
        pass
    else:
        sys.exit(try_unmount(vol))
        pass

    sys.exit(0)
    pass

if __name__ == '__main__':
    cli()
    sys.exit(0)
    pass

@permezel
Copy link

permezel commented Dec 7, 2023

I should add some more/better usage.

┌──(dap  hubris)-[~]
└─% smb-mount --help 
Usage: smb-mount [OPTIONS] COMMAND [ARGS]...

Options:
  --help  Show this message and exit.

Commands:
  auth       Establish the authentication.
  auth-pass  Establish authentication, password from `pass`.
  check      Verify authentication.
  mount      Mount volume.
  unmount    Unmount volume.

The auth-pass may only be useful if you use pass and adopt the same scheme as I do.
Alternately, there is the pass sub-command.

┌──(dap  hubris)-[~]
└─% smb-mount auth --help
Usage: smb-mount auth [OPTIONS]

  Establish the authentication. I keep the user passwords in `pass -c
  Auth/SMB/${server}/${user}`. However, this requires interaction with the
  YubiKey for each access. Accordingly, I cache them in the system keychain,
  presumably protected by the keychain unlock password (ie: login password).

Options:
  --server TEXT    SMB server address
  --user TEXT      SMB user name
  --password TEXT  SMB password
  --help           Show this message and exit.

To add a new key-chain entry:

┌──(dap  hubris)-[~]
└─% smb-mount auth       
Enter SMB server address [nas.local]: my-local-nas.local
Enter SMB username [dap]: some-other-user
Password: 
Repeat for confirmation: 
┌──(dap  hubris)-[~]
└─%

Subsequently, you have to edit the python script and add in the desired mounts, using my-local-nas.local in the server column and some-other-user in the user column of the table.

It would be possible to adjust things so that /nas/${user} was owned by ${user} and each user had their own copy of the smb-mount script and then /nas/${user}/home would possibly be only accessible to that user (controled by the permissions on /nas/${user}/..

@permezel
Copy link

permezel commented Dec 7, 2023

So I get mounts maintained as:

└─% df -h
Filesystem                                                     Size    Used   Avail Capacity iused ifree %iused  Mounted on
/dev/disk3s1s1                                                1.8Ti   9.2Gi   1.2Ti     1%    390k  4.3G    0%   /
...
//dap@nas.local/video                                          28Ti    13Ti    15Ti    47%     14G   16G   46%   /System/Volumes/Data/nas/video
//dap@nas.local/shared                                        100Gi   1.3Gi    99Gi     2%    1.4M  103M    1%   /System/Volumes/Data/nas/shared
//nimda@nas.local/home                                         28Ti    13Ti    15Ti    47%     14G   16G   46%   /System/Volumes/Data/nas/nimda
//dap@nas.local/nihongo                                       128Gi    87Gi    41Gi    68%     91M   43M   68%   /System/Volumes/Data/nas/nihongo
//dap@nas.local/tax                                            28Ti    13Ti    15Ti    47%     14G   16G   46%   /System/Volumes/Data/nas/tax
//dap@nas.local/music                                         4.0Ti   892Gi   3.1Ti    22%    935M  3.4G   22%   /System/Volumes/Data/nas/music
//dap@nas.local/home                                           28Ti    13Ti    15Ti    47%     14G   16G   46%   /System/Volumes/Data/nas/dap

This works better than for @unspecified-cohabitating-individual who, on a separate machine, where @insert-anonymising-pronoun is always complaining to me that the NAS shares keep disappearing and forces Finder.app to restart to recover.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment