Native macOS menu-bar editor for /etc/hosts. Personal use, Apple Silicon.
- Install XcodeGen:
brew install xcodegen
- Generate the Xcode project:
xcodegen generate
- Open
Facade.xcodeprojin Xcode and press ⌘B (Release build). The post-build phase copies the app into/Applications/Facade.app. - Launch
Facadefrom Spotlight. Ad-hoc signed, so if Gatekeeper complains on first launch: right-click → Open to bypass. - (Optional, recommended) Install the passwordless helper so saves don't prompt:
You'll type your password once. After that, every save is instant.
./scripts/install-privileged.sh
The .xcodeproj and Info.plist are generated from project.yml — they're git-ignored. Re-run xcodegen generate if you change project.yml or add source files.
- SwiftUI
MenuBarExtraapp (LSUIElement=true, no Dock icon). - Reads
/etc/hostsdirectly (world-readable). - Saves via one of two paths, depending on what's installed:
- Helper mode (after running
install-privileged.sh): app pipes the new contents tosudo -n /usr/local/bin/facade-save, which writes the file atomically and flushes DNS. No password prompt. - Fallback mode (helper not installed): app writes to a temp file
and runs
osascript ... with administrator privilegestocpit into place. Shows the standard admin password prompt; macOS caches the authorization for ~5 minutes so bursts of saves are silent.
- Helper mode (after running
- DNS flush uses
killall mDNSResponder(SIGTERM → launchd respawns it with a clean cache), notkillall -HUP. HUP only tells mDNSResponder to reload config; existing cached DNS answers still win over a newly-added/etc/hostsentry. A full restart flushes them. - On launch the app terminates any older Facade instances so you only ever have one menu-bar icon. Xcode launches from DerivedData bypass LaunchServices' normal single-instancing, so we enforce it manually.
scripts/install-privileged.sh:
- Installs
scripts/facade-saveto/usr/local/bin/facade-save, owned byroot:wheelwith mode 755 (so only root can modify it). - Drops a sudoers fragment at
/etc/sudoers.d/facadegranting only the current userNOPASSWDfor only/usr/local/bin/facade-save.
That single command is the only thing that runs without a password — it doesn't grant shell access or arbitrary root. The helper also refuses to write an empty file as a sanity check.
Tradeoff: any process running as your user can now write /etc/hosts and
flush DNS without prompting. For a personal dev machine this is a
reasonable tradeoff. On a shared or managed-by-IT machine, skip the
installer and live with the fallback prompt.
sudo rm /usr/local/bin/facade-save /etc/sudoers.d/facadeFacade automatically falls back to the osascript prompt once the helper is gone.
- Source changes → ⌘B in Xcode → post-build phase re-installs to
/Applications. - Helper script changes →
sudo install -o root -g wheel -m 755 scripts/facade-save /usr/local/bin/facade-save. project.ymlchanges →xcodegen generate, then ⌘B.
project.yml # XcodeGen config — source of truth
Facade/
FacadeApp.swift # @main, MenuBarExtra scene, single-instance enforcement
HostsEditorView.swift # editor UI, save/revert/quit
HostsFile.swift # read, helper-or-osascript privileged write
scripts/
facade-save # root helper: stdin → /etc/hosts + DNS flush
install-privileged.sh # one-time sudoers + helper installer