Omahub
← All plugins
M

Fan

by moerdowo

Manual fan speed control for Apple SMC laptops

Security review

Review recommended · 2 findings

Deterministic scan — not a security guarantee

Low
Risk level
Low
Analyzed commit
75aae84
Scanned
1 month ago

Flagged patterns appear only in documentation files (README / docs) — descriptive examples, not executable code.

  • Docs sudo README.md:107

    Command runs with sudo, elevating the process beyond the plugin environment.

    sudo rm -f /etc/udev/rules.d/90-applesmc-fan.rules /etc/polkit-1/rules.d/49-mbpfan.rules
  • Docs sudo README.md:108

    Command runs with sudo, elevating the process beyond the plugin environment.

    sudo udevadm control --reload-rules

Automated analysis only — not a security guarantee.

AI advisory review

No obvious issues detected

Language-model assessment · ~deepseek/deepseek-v4-flash-latest — advisory only

Low
AI risk level
Low
Recommendation
install
Model
~deepseek/deepseek-v4-flash-latest
Analyzed commit
75aae84
Reviewed
1 month ago

The plugin is a fan control widget for Apple SMC laptops. It requires sudo to install udev and polkit rules, which is clearly documented and necessary for its function. The code is transparent, well-commented, and includes security measures like descriptor-based file access. No malicious behavior found.

  • Requires sudo to install system-level privileges, but this is clearly documented and scoped to fan control.
  • Potential for thermal damage if user sets fans to low speed, but this is user-controlled and warned about.
How this check works

This review combines the deterministic scan (the rule-based results above) with an independent look at the plugin's code by a language model. The model reads a trimmed sample of the repository's files, the manifest, and the README, then gives a plain-language risk level and a recommendation: install (no notable danger), review (look closer first), or avoid (clearly dangerous).

It runs on the same analyzed commit as the deterministic scan and is strictly advisory — it is not a security guarantee and never blocks a plugin by itself. A human moderator still reviews plugins before they are listed.

AI advisory only — automated analysis, not a security guarantee.

Install
$ omarchy plugin add https://github.com/moerdowo/omarchy-mac-fan-control --enable
Hardware #bar #power-management #system

Fan — manual fan speed control for Omarchy

A bar widget for Apple SMC laptops (applesmc) that shows live fan RPM and lets you take the fans off their automatic curve.

Built for and tested on a MacBook Pro 13,3 running Omarchy 4.0.0.

<img src="preview.png" alt="The Fan panel open below the bar, showing mode buttons, a speed slider, per-fan RPM and CPU temperature" width="360">

Install

omarchy plugin add https://github.com/moerdowo/omarchy-mac-fan-control.git --enable --yes
sudo ~/.config/omarchy/plugins/io.github.moerdowo.fan/install-privileges.sh

The first command clones the plugin to ~/.config/omarchy/plugins/io.github.moerdowo.fan/ and puts the widget in the right bar section. The second grants the two privileges the controls need — read Setup before running it, and read install-privileges.sh itself if you'd rather not take my word for what it writes. Plugins run as unsandboxed code inside omarchy-shell.

What controls these fans

Three things can drive an Apple SMC fan, and they do not cooperate:

  • the SMC itself, which has its own thermal curve baked into firmware
  • mbpfan, a userspace daemon that pins the SMC into forced mode and rewrites fanN_output from CPU temperature once a second
  • anything else writing fanN_output, which mbpfan overwrites on its next tick

On a machine where mbpfan is running — it is on this one, enabled by default — setting an RPM by hand lasts about a second. So manual mode here means stopping mbpfan and owning the fans, and auto mode means starting it again. If mbpfan is not installed, auto instead clears the forced-mode bit and hands the fans back to the SMC's own curve.

Setup

Two privileges are needed so that dragging the slider never pops an auth dialog. Install them once:

sudo ~/.config/omarchy/plugins/io.github.moerdowo.fan/install-privileges.sh

That writes:

File Grants
/etc/udev/rules.d/90-applesmc-fan.rules group wheel write access to the applesmc fan attributes
/etc/polkit-1/rules.d/49-mbpfan.rules group wheel may start/stop mbpfan.service without a password

The polkit rule is scoped to that one unit and to local, active sessions. Until the script is run, the panel still shows live RPM and temperature — it just displays a setup notice instead of the controls.

To undo everything, see Removing it.

Requirements

Needs Why
An Apple SMC laptop with the applesmc kernel module loaded The widget reads and writes /sys/devices/platform/applesmc.*/fan*
Omarchy Quattro (omarchy-shell) The plugin is a Quickshell bar widget
wheel group membership Both privilege rules are granted to wheel
python3 bin/fanstate, which is how the saved mode is read and written

mbpfan is optional. If it is installed and running, auto mode hands the fans back to it; if it is not, auto clears the SMC's forced-mode bit instead. Nothing else is pulled in — bin/fanctl is a shell script, bin/fanstate is a standard-library-only Python script beside it, and the plugin bundles no third-party code. Without python3 the fans still work from the panel and the CLI; only the saved mode is lost, so restore and the suspend heal have nothing to act on.

Removing it

Give back the privileges first, then remove the plugin:

sudo ~/.config/omarchy/plugins/io.github.moerdowo.fan/uninstall-privileges.sh
omarchy plugin remove io.github.moerdowo.fan --yes

The first command deletes the udev and polkit rules the setup step wrote, reloads udev, restores root-only ownership on the fan attributes this session already relaxed, starts mbpfan.service again so the fans go back under automatic control, and deletes the saved mode in ~/.local/state/omarchy-fan/ (it prints a note if another account on the machine has its own copy). The second removes the widget from the bar and deletes ~/.config/omarchy/plugins/io.github.moerdowo.fan/. Nothing is left outside those three locations — no units, no files on PATH. If you symlinked fanctl into ~/.local/bin, delete that symlink too.

The saved mode is the one piece of state the plugin writes, and it is worth clearing deliberately: a manual target left behind is a setting a future install would find and could act on. fanctl forget removes it at any time without touching the privilege rules.

Run the uninstall script before removing the plugin: it lives inside the plugin directory and goes away with it. If you removed the plugin first, the two rule files can be deleted by hand:

sudo rm -f /etc/udev/rules.d/90-applesmc-fan.rules /etc/polkit-1/rules.d/49-mbpfan.rules
sudo udevadm control --reload-rules
rm -rf ~/.local/state/omarchy-fan

Using it

The widget sits in the right bar section.

Interaction Effect
left click open the panel
right click toggle max ↔ auto
scroll ±5%

In the panel: Auto / Manual / Max picks the mode, the slider sets the speed, and the readout shows each fan's real RPM plus CPU package temperature. j/k move between sections, h/l adjust, Enter activates, Esc closes.

The bar glyph is tinted whenever the fans are held off their thermal curve, so a manual setting you forgot about is visible at a glance.

0% is not "off". Percent spans each fan's min..max range — 2160–5927 RPM on the left fan, 2000–5489 on the right. 0% is the slowest the hardware will run.

CLI

The panel shells out to bin/fanctl for everything, so the same control is available from a terminal:

fanctl status          # JSON: mode, percent, per-fan RPM, CPU temp
fanctl set 60          # hold both fans at 60% of their range
fanctl max             # 5927 / 5489 RPM
fanctl auto            # hand back to mbpfan (or to the SMC)
fanctl restore         # re-apply the saved mode
fanctl forget          # drop the saved mode and go back to auto

set, max and auto are you asking for something, and are obeyed as asked. restore and the panel's status --heal re-apply a setting you are not present for, so they are gated — see Suspend and Safety.

Symlink it onto your PATH if you want it there:

ln -s ~/.config/omarchy/plugins/io.github.moerdowo.fan/bin/fanctl ~/.local/bin/fanctl

Suspend

The SMC drops out of forced mode across a suspend, so a manual setting is lost on resume. Rather than install a sleep hook, the panel's status poll calls fanctl status --heal, which notices the drift and re-applies the saved mode. Worst case the fans idle for one poll interval after you open the lid.

That heal runs unattended on a timer, so it is deliberately timid. It will not act when:

  • mbpfan is running. Something with a temperature reading already has the fans; stopping it to reinstate a manual target would remove the only thermal supervision on the machine without anyone asking.
  • the saved target predates the current boot. A target is stamped with the boot it was set in. Anything older — including a mode left over from a previous install of this plugin — is a stale wish, not the state the machine is expected to be in, and only an explicit fanctl restore will act on it.
  • the CPU is at or above the failsafe temperature. See below.

Safety

Manual mode means nothing is watching the temperature — not the SMC, not mbpfan. Holding the fans at 0% under a sustained load is a real way to cook the machine. The CPU package temperature is shown in the panel for that reason. Auto is the safe default; use manual for a known workload and put it back afterwards.

Because that is true, anything that puts the fans into manual mode without you asking right then has a temperature failsafe. If the CPU package is at or above 85 °C, neither status --heal nor restore will hold the fans below maximum: they hand the fans back to mbpfan (or to the SMC), and the panel says THERMAL FAILSAFE with the bar glyph tinted until you pick a mode. The threshold is FANCTL_FAILSAFE_TEMP in the environment if you want a different one.

The failsafe does not second-guess a mode you just chose — fanctl set 0 and the panel slider do what you tell them, hot or not. It only governs the unattended paths.

The kernel's own thermal throttling still applies regardless of fan mode.

The saved mode

~/.local/state/omarchy-fan/mode is the only thing the plugin writes, and --heal and restore decide from it whether to stop mbpfan and hold the fans unattended — so it is read as something another process running as you could have got to first. Checking a path and then opening it is two operations against a name, and between them the name can be made to mean something else: the file replaced, or any directory on the way to it. Mode 0700 does not answer that; it keeps other accounts out, and the process doing the swapping would be this one.

The shell cannot close that gap — it has no redirection that opens O_NOFOLLOW or O_NONBLOCK, and no openat, renameat or unlinkat. So the filesystem work lives in bin/fanstate, which fanctl starts once and keeps alive for the run. It walks the path a component at a time, each step an openat on the descriptor of the one before it and each descriptor validated by fstat, and then reads, writes, replaces and deletes relative to the descriptor that walk ended on. The directory is never named again, so a swap between two operations reaches a name nothing is looking at. Files open O_NOFOLLOW | O_NONBLOCK and are checked on the descriptor open returned, so a symlink or a FIFO left at the name yields nothing rather than the wrong bytes or a stalled panel; writes land by renameat from an O_EXCL temporary. fanctl then acts on the result only if it reads as exactly auto or manual: and a percentage in range.

A path component that anyone but you can write to is refused rather than used, so a group- or world-writable $XDG_STATE_HOME means no saved mode — the panel and the CLI still work, restore and the heal simply have nothing to act on.