mountctl is a small macOS helper for:
- forcing the London office DNS server to the front of the resolver list
- mounting reachable London developer machines with SSHFS
- restoring the previous DNS state and unmounting everything cleanly when you leave the office network
The script is intended for a workstation that moves between office and non-office networks. It installs itself as a LaunchAgent and then re-runs periodically so the mount and DNS state follows the machine automatically.
mountctl manages two pieces of state:
- DNS
It detects active macOS network services on the configured office subnet prefix, defaulting to
10.20.*, and moves10.20.40.200to the front of the DNS order so*.office.xyzzy.tools.internalresolves correctly. - SSHFS mounts
It probes the London developer blades and mounts the reachable ones under
~/mnt.
When it changes DNS, it first stores the original resolver state in /tmp/mountctl-dns. That lets stop and dns-down restore the previous manual DNS list or automatic DHCP mode.
mountctl expects:
- Homebrew
- Homebrew Bash at
/opt/homebrew/bin/bash - macFUSE
- SSHFS from the macFUSE project at
/usr/local/bin/sshfs sudoaccess for installing/usr/local/bin/mountctland changing macOS DNS settings
Current install sources:
- Homebrew:
https://brew.sh/ - Bash:
brew install bash - macFUSE:
https://macfuse.io/ - SSHFS package:
https://macfuse.github.io/2025/11/11/SSHFS-3.7.5.html
mountctl install checks those dependencies before it writes anything to /usr/local/bin or ~/Library/LaunchAgents.
If mountctl install detects the deprecated Homebrew sshfs at /opt/homebrew/bin/sshfs, it will warn but continue. That path is treated as a temporary fallback only; the preferred setup is still the macFUSE SSHFS package at /usr/local/bin/sshfs.
The LaunchAgent and the start/dns-up paths call sudo networksetup ... when they need to modify DNS. For unattended background operation, your user needs to be able to run the required sudo commands non-interactively. If your Mac prompts for a password instead, manual commands may still work in an interactive shell, but the background LaunchAgent will not be able to repair DNS state on its own.
mountctl supports a small set of environment overrides:
HOSTS: override the managed host list with a space-delimited subsetDNSSERVER: override the office DNS server IPOFFICE_IP_PREFIX: override the office network IP prefix that marks a service as "in office"REMOTE_PATH: override the remote path that SSHFS mountsSSHFS_BIN: override the SSHFS binary pathSCRIPT_COLOUR: set tooff,false, or0to disable colour output
Defaults:
HOSTS="dev01 dev02 dev03 dev04 dev05 dev06 dev07 dev08"DNSSERVER=10.20.40.200OFFICE_IP_PREFIX=10.20.REMOTE_PATH=""which mounts the remote login user's home directorySSHFS_BIN=/usr/local/bin/sshfswhen the macFUSE SSHFS package is installed, otherwise/opt/homebrew/bin/sshfsif only the deprecated Homebrew SSHFS is present
Install-time overrides are written into the generated LaunchAgent. If you want the background agent to keep using custom HOSTS, DNSSERVER, OFFICE_IP_PREFIX, REMOTE_PATH, SSHFS_BIN, or SCRIPT_COLOUR values, set them when you run mountctl install.
Clone the repo somewhere under your home directory, then run:
./mountctl installThat command:
- installs the script to
/usr/local/bin/mountctl - writes
~/Library/LaunchAgents/tools.xyzzy.mountctl.plist - reloads the LaunchAgent into the current GUI session
- persists any install-time environment overrides into the LaunchAgent
It also assumes the invoking user can run the required sudo operations for /usr/local/bin installation and DNS updates without getting stuck on an unattended password prompt.
The LaunchAgent is configured with:
RunAtLoad = trueStartInterval = 120KeepAlive -> NetworkState = true
So after installation it will re-run automatically at login, on network changes, and every two minutes.
mountctl start
mountctl stop
mountctl statusAvailable commands:
install: install or refreshmountctland its LaunchAgentbootstrap: alias forinstallstart: DNS-up plus mountstop: DNS-down plus unmountstatus: show current DNS settings and managed mountsrun: LaunchAgent mode; auto-apply in the office and auto-clean up elsewhereup: alias forstartdown: alias forstopdns-up: only change DNSdns-down: only restore DNSmount: only mount reachable hostsunmount: only unmount managed hostshelp: show command help
Use the defaults:
mountctl startOverride the office DNS server IP:
DNSSERVER=10.20.40.200 mountctl dns-upOverride the managed host list:
HOSTS="dev07 dev08" mountctl mountOverride the office network prefix match:
OFFICE_IP_PREFIX=10.20. mountctl statusOverride the remote path mounted over SSHFS:
REMOTE_PATH=/var/tmp mountctl mountOverride the SSHFS binary path:
SSHFS_BIN=/custom/path/sshfs mountctl startPersist a custom host subset and DNS settings into the LaunchAgent:
HOSTS="dev07 dev08" DNSSERVER=10.20.40.200 OFFICE_IP_PREFIX=10.20. ./mountctl installCombine multiple overrides:
HOSTS="dev07 dev08" OFFICE_IP_PREFIX=10.20. DNSSERVER=10.20.40.200 REMOTE_PATH=/srv/shared mountctl startBy default mountctl mounts the remote login user's home directory.
You can override that with REMOTE_PATH:
REMOTE_PATH=/some/remote/path mountctl startMount points are created under:
~/mnt/<short-hostname>
For example:
~/mnt/dev07
~/mnt/dev08
On a London office network, mountctl rewrites the active network services whose IPs match OFFICE_IP_PREFIX, defaulting to 10.20.*, so the DNS list begins with:
10.20.40.200
That is necessary because the corporate resolver can return NXDOMAIN for office.xyzzy.tools.internal, and macOS will not reliably fall through to the second DNS server after a negative response.
You can override the default office DNS server IP when needed:
DNSSERVER=10.20.40.200 mountctl dns-upYou can also override the office-network match prefix:
OFFICE_IP_PREFIX=10.20. DNSSERVER=10.20.40.200 mountctl startWhen you run stop, dns-down, or when the LaunchAgent notices the machine is no longer on the office network, mountctl restores the previous DNS state from its saved state files.
mountctl writes logs to:
/tmp/mountctl.out/tmp/mountctl.err
This project is licensed under the GNU Affero General Public License v3.0 (AGPL-3.0).
- LICENSE: full license text
- LICENSE.md: short license summary
- NOTICE.md: notices and attributions