English · 简体中文
A tiny macOS menu bar app that keeps your Mac awake — even with the lid closed.
Built for long unattended runs: agent loops, builds, model training, big downloads.
- 🔥 Menu bar toggle — click a preset (30m / 1h / 2h / 4h), custom duration, or forever
- 🌙 Turn off display while keeping awake — press the lid-close button and walk away
- 🔌 Lid-close-aware — when plugged in, your Mac stays awake with the lid shut
- 🔋 Battery mode — opt-in advanced override (
pmset disablesleep) lets the lid-closed-awake trick work on battery too - 🛡️ Battery safety rail — auto-stops at a low-battery threshold (default 20%) so an unattended run can't drain your battery to zero
- 🔐 One password prompt, ever — the advanced mode installs a tiny
sudoersfragment so subsequent uses are silent - ⏱️ Smart timer — "Until 23:00" picks the next occurrence; if it already passed today, rolls to tomorrow
- 💤 Graceful exit — auto-restores normal sleep policy when the timer expires, you stop manually, or the app crashes
Grab the latest Bonfire.dmg from Releases → double-click → drag Bonfire onto the Applications shortcut.
First launch: macOS Gatekeeper blocks ad-hoc-signed apps. Either:
- Right-click the app → Open → Open in the dialog that appears, or
- Run once:
xattr -dr com.apple.quarantine /Applications/Bonfire.app
That's a one-time step. After the first launch macOS remembers.
Click the campfire icon in your menu bar:
Keep your Mac awake for a fixed time — click 30m, 1h, 2h, 4h, or open Custom ▾ to dial any duration. Bonfire flips to the burning view showing a live countdown. When the timer expires it stops automatically and your Mac returns to normal sleep behavior.
Keep your Mac awake indefinitely — click Keep awake. Click Stop when you're done.
Pre-emptively turn the screen off (while keeping the system running) — in the burning view, click Turn off display. Useful before closing the lid and walking away. Move the mouse or press a key to wake the screen again.
- Your Mac stays awake the whole time, even if you don't touch it.
- The screen may still dim or turn off on its own — that's fine, your Mac keeps running underneath.
- Closing the lid keeps it running too — but only when plugged in. On battery, closing the lid still puts your Mac to sleep, unless you opt into the advanced battery mode below.
- On battery, Bonfire stops itself when battery drops below your threshold (default 20%) so an unattended run can never drain to zero.
- The timer survives lid close, screen off, and your absence — it lives in the menu bar app's process, not in anything UI-bound.
By default, macOS forces sleep when you close the lid on battery — there's no way around this from a normal app. Bonfire offers an opt-in workaround:
- Open
Preferences…→ toggle on Keep awake on battery with lid closed. - The next time you start a session on battery, Bonfire asks for your admin password once to install a small
sudoersfragment. - From then on, lid-closed-on-battery just works, silently, forever — across app restarts and reboots.
The fragment grants the admin group passwordless access to exactly the two pmset commands Bonfire needs, nothing else:
%admin ALL = (root) NOPASSWD: /usr/bin/pmset -b disablesleep 0, /usr/bin/pmset -b disablesleep 1
To revoke at any time: sudo rm /etc/sudoers.d/bonfire-pmset.
Two layers of awake-keeping that stack:
| Layer | API | Effect | Privileges |
|---|---|---|---|
| Idle / system sleep | IOPMAssertion (PreventUserIdleSystemSleep + PreventSystemSleep) |
Stops the system from idle-sleeping. On AC power this is enough to keep a closed lid awake. | None |
| Forced lid-close sleep on battery | pmset -b disablesleep 1 via passwordless sudo |
Overrides macOS's hardware-level "lid closed on battery → sleep" policy. | Admin (one-time install) |
Display dimming uses pmset displaysleepnow (no admin required).
Full design rationale and trade-offs: docs/design.md.
Requires Xcode 15+ and Homebrew.
brew install xcodegen
git clone https://github.com/<your-user>/bonfire.git
cd bonfire
xcodegen
open Bonfire.xcodeprojOr for a distributable build:
./scripts/build.sh
# → dist/Bonfire.app, dist/Bonfire.zip, dist/Bonfire.dmgRun the full test suite:
xcodebuild -project Bonfire.xcodeproj -scheme Bonfire -destination 'platform=macOS' test49 unit tests covering the state machine, timer math, preferences persistence, and IOKit wrappers.
Bonfire/
├── BonfireApp.swift @main entry
├── AppIcon.icns App icon (regenerate via scripts/make-icon.sh)
├── Core/
│ ├── BonfireDomain.swift State enums
│ ├── BonfireController.swift State machine (testable, mocked deps)
│ ├── AssertionManager.swift IOPMAssertion wrapper
│ ├── PowerMonitor.swift IOPS power source events
│ ├── Notifier.swift UserNotifications wrapper
│ ├── BatteryAwakeBypass.swift pmset + sudoers fragment installer
│ └── DurationCalculator.swift "Until" rollover math
├── Support/
│ ├── Preferences.swift @AppStorage-backed config
│ ├── LaunchAtLogin.swift SMAppService wrapper
│ ├── IconRenderer.swift Menu bar icon (PNG + fallback)
│ ├── Display.swift pmset displaysleepnow
│ └── WindowAccessor.swift SwiftUI ↔ NSWindow bridge
├── Views/
│ ├── PopoverView.swift State router (idle vs burning)
│ ├── IdleLayout.swift Quick presets + custom + forever
│ ├── BurningLayout.swift Countdown + stop + display-off
│ ├── PreferencesView.swift Settings window
│ └── InfoView.swift "How it works" window
└── Resources/
├── burning.png Menu bar icon (burning state)
└── idle.png Menu bar icon (idle state)
Architecturally every IOKit-touching component sits behind a protocol so the state machine can be unit-tested without actually putting the system to sleep.
Bonfire's footprint outside /Applications is minimal:
~/Library/Preferences/ai.dotwise.Bonfire.plist— user settings/etc/sudoers.d/bonfire-pmset— only if you enabled "Keep awake on battery with lid closed". Remove with:sudo rm /etc/sudoers.d/bonfire-pmset
SMAppServicelogin item — removed automatically when you delete the app
- Ad-hoc signed, not notarized. Gatekeeper warns on first launch; right-click → Open bypasses it.
- Battery + lid closed mode modifies system-wide
pmsetpolicy. If the app crashes mid-session, the next launch will silently reset it; if you remove the sudoers fragment manually, runsudo pmset -b disablesleep 0to be safe. - External display + lid closed without bypass already enters "clamshell mode awake" via macOS itself when on AC — Bonfire is redundant but harmless in that setup.
MIT. See LICENSE.