Run Homebrew services from a dedicated user without logging into it (relevant for macOS only)
|
4 months ago | |
---|---|---|
cmd | 4 months ago | |
.gitignore | 4 months ago | |
LICENSE | 4 months ago | |
LICENSE_HOMEBREW | 4 months ago | |
README.md | 4 months ago |
This is a qt lil h4cc to get homebrew-services
to bnice when running things from a dedicated Homebrew user, without "physically" logging into said user (i.e. run shit completely backgrounded). This requires special handling on macOS because it runs daemons/services differently than papi Linux.
Since the need for such background services originated from my multi-user but single-Homebrew setup, I'll also be explaining about how to set that up.
Kkkkkkkkkkkkkk so to clarify the exact use case this shit was made for:
brew
. While you could have separate instances many pre-compiled "bottles" will instead have to be compiled from scratch, plus you'll have dupes of anything else regardless.muhbrew
) that will be used for managing the Homebrew instance and really doing anything else Homebrew-related.su -l muhbrew -c 'brew install foo'
, which is not only fairly long but also will keep asking for a password.launchd
/launchctl
shit generally assumes there's a GUI session for the user launching services. SSHing into the third user's account or going through su
/sudo
doesn't spawn a GUI session, and "physically" logging into the account just for Homebrew is too much hassle. So we need to explicitly tell macOS to launch something in a true background session.LimitLoadToSessionType
with all of these Aqua
, Background
, LoginWindow
, StandardIO
, System
. This was necessary to begin with because without that key, macOS only allows Aqua
-based shit to run (which always requires a GUI session). su(do)
ing to the Homebrew user doesn't spawn an Aqua
session, rather it's Background
.launchctl
uses "domains" for launching shit in. These contain the user's UID (e.g. 501
), which for Aqua
sessions results in gui/501
and for background-type shit it's user/501
. The user
domain exists when any process belonging to a user is spawned, e.g. a shell through SSH or su(do)
.brew services
command and make sure it passes along a domain suitable for Background
sessions.brew bundle
.PATH
s as well./etc/sudoers.d/homebrew
and add the following line for all non-Homebrew accounts (replace muhuser
and muhbrew
with the actual account names obviously (whoami
)):muhuser ALL=NOPASSWD: /usr/bin/su -l muhbrew -c *
Instead of simply aliasing the brew
command, we'll need a function in the shell's profile. Obviously, do not add this for the Homebrew user itself. Also this was made for bash
because I prefer it over zsh
, so it may need some adjusting for other shells.
brew() {
sudo su -l muhbrew -c "source \$HOME/.bash_profile; brew $*"
}
Note how I'm doing sudo su
instead of just straight sudo
. Of course using just su
wouldn't pass through sudoers
config, and there's a major problem with using sudo
. Either it doesn't set up the environment (not the problem I meant), or launchctl detects the wrong login session type (Aqua
when it should've been Background
) and the service won't even start. Like, sudo -u muhbrew bash -c 'brew etctwtceccetfec'
will cause Aqua
being wrongly detected. Finally, I'm using su -l
to discard the current environment, which again is needed to get it detected as a Background
session. That also means I have to manually source muhbrew
's shell profile, since you would put your Homebrew-related environment variables in there and those would need to be exported for the brew
command. I'm pretty sure sudo
simply overrides the "interactivity" of su
's shell and causes it to no longer read profiles automatically, but instead goes looking for an environment variable BASH_ENV
(or ENV
for sh, neither of which usually exist). May as well just source the file directly then. =]
With this setup you can run brew services
commands as any of the configured user and the service will be able to start regardless of whether you're in an actual Aqua
session, or doing some su(do)
magic. The sudoers
config actually allows any command to be executed as muhbrew
, but your own accounts are likely admins while muhbrew
is not, so there's not really any risk.
And a final note: keep in mind that although Homebrew installs a LaunchAgent
that would normally auto-start the service when rebooting, it won't actually trigger if you're not logging in to an Aqua
session for muhbrew
. So you'll always have to manually run a brew services start
command, or write a LaunchDaemon
that runs the necessary brew
commands.
Luckily I have such a LaunchDaemon
. ;]];];];] It will be run as r00t so it doesn't really matter where you put the script.
You'll need one script, named e.g. b00t.sh
(extend the brew_svc
array with other services you wanna start):
#!/bin/bash
brew_svc=(httpd)
for svc in "${brew_svc[@]}"; do
sudo su -l muhbrew -c "source \$HOME/.bashrc; brew services start $svc"
done
And the service definition, e.g. /Library/LaunchDaemons/com.jemoeder.lief.b00t.plist
(adjust the file names and the plist's Label
and Program
properties as needed =]):
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.jemoeder.lief.b00t.plist</string>
<key>Program</key>
<string>/Users/muhuser/b00t/b00t.sh</string>
<key>Disabled</key>
<false/>
<key>KeepAlive</key>
<false/>
<key>LaunchOnlyOnce</key>
<true/>
<key>RunAtLoad</key>
<true/>
<!--
<key>StandardOutPath</key>
<string>/tmp/keks.log</string>
<key>StandardErrorPath</key>
<string>/tmp/keks.err</string>
-->
</dict>
</plist>
Run launchctl load /Library/LaunchDaemons/com.jemoeder.lief.b00t.plist
to register that shit. It will also immediately run.
Since it's a Daemon instead of Agent it only runs when the cump00per boots up, and usually before users even have a chance to log in.
services
base command by running brew services
(will automatically install em if needed).gui
domain to fuck off.brew tap wazakindjes/homebrew-services-bg https://gitgud.malvager.net/Wazakindjes/homebrew-services-bg
Now when you run any brew services
command it will be overridden by the version from this repo, and do its magic to start shit in a user
domain. [=[=[=[==[
brew commands
lists services
twice under External commands
, deal w/ it (it has to be eggzactly the same to even override it obviously). ;]];;]];];Aqua
context and brew services stop
will fail/seem to hang. launchctl bootout gui/$UID <service name>
can be used to get it unstuck. The service name can be derived from the plist, e.g. ~/Library/LaunchAgents/homebrew.mxcl.httpd.plist
becomes homebrew.mxcl.httpd
(plist basename without extension lol).