OS X 下让 supervisor 开机启动,以及权限、环境变量、codesign 问题
项目使用到一套自己搭建的 web 操作平台。该平台用 python+flask+redis+git+svn 进行自动化操作。其启动脚本采用的是 supervisor 来管理。
今天想让开机自动运行 supervisor,于是进行了一番尝试。
我们的目标
当然,不是 “没有蛀牙”。
下文模拟的目标是
系统启动时 无需 登陆,用 supervisord 启动某 supervisord.conf。
看似很简单,其实踩了不少坑。
- 添加 plist 脚本
1 | sudo vim /Library/LaunchDaemons/com.agendaless.supervisord.plist |
复制以下内容
1 |
|
接着注册 plist
1 | sudo launchctl load /Library/LaunchDaemons/com.agendaless.supervisord.plist |
重启之后就发现可以自动运行了。
But, 事情哪有那么简单
首先解答下为何放在这个目录,以及 agent 和 daemon 的区别
根据 apple 文档,plist 文件可以存在于如下几个地方:
类型 | 位置 | 执行条件 |
---|---|---|
用户的 agent | ~/Library/LaunchAgents | 当前登录的用户 |
全局 agent | /Library/LaunchAgents | 当前登录的用户 |
全局 Daemons | /Library/LaunchDaemons | root 或者用 User 制定的用户 |
系统 agent | /System/Library/LaunchAgents | 当前登录的用户 |
系统 daemon | /System/Library/LaunchDaemons | root 或者用 User 制定的用户 |
Agent 和 Daemon 最大的区别在于,Agent 是需要用户登录才能触发,Daemon 是不需要用户登录。
这样,排除 system 用的目录,只剩下全局 Daemons (Global agent),也就是 /Library/LaunchDaemons
了。
But, 事情哪有那么简单
这样启动 supervisor 之后,是默认用的 root 用户启动。所以,当你的脚本中有用到类似于 Python 中 os.path.expanduser('~')
的命令时,其实你拿到的是 /Users/root
。
这一定不是你想要的结果。
根据 supervisor 的文档,你可以指定 user
1 | [program:test] |
But, 事情哪有那么简单
查看 supervisor 的源代码,这个命令只是把进程移交给了这个 user。
os.path.expanduser('~')
这种命令,是获取的环境变量 $HOME
。而 user=yourname
并不会影响任何环境变量。没错,现在 os.path.expanduser('~')
的结果应该还是 /Users/root
,但是如果你执行 print os.system("whoami")
那就会是 yourname
。
so? byebye supervisor。 我转而查找 launchd 的方法。
launchd 这个玩意是 mac 用来替代 cron 的。我想,一定不会太挫吧。于是在开发文档中又看到:
UserName <string>
This optional key specifies the user to run the job as. This key is only applicable when launchd is running as root.
哦,只在 root 触发时才有用。没问题。另外也发现了 UserGroup 这个 key,大同小异。那我们加上:
在 /Library/LaunchDaemons/com.agendaless.supervisord.plist
中最外层 dict 里面添加:
1 | <key>UserName</key> |
然后直接暴力 sudo reboot
(其实也可以先用 launchctl unload com.agendaless.supervisord.plist
再 launchctl load com.agendaless.supervisord.plist
)
ok,程序进来了,一切 ok。os.path.expanduser('~')
也对了。
But, 事情哪有那么简单
可能到这儿对大多数人都没问题了。但是我的程序是要用这套东西 build ios 和 android 的游戏。
命令中有一句,xcodebuild
,是在一个 Makefile 中,这个 Makefile 的执行,是靠 os.system
执行。
当程序跑到这里时,会报出错误:
1 | [BEROR]Code Sign error: Provisioning profile does not match bundle identifier: The provisioning profile specified in your build settings (“farm_dev”) has an AppID of “com.wemomo.game.farm” which does not match your bundle identifier “com.ejoy.test.farm”. |
根据网上内容,可能由于钥匙串没有解锁。所以我们用
1 | os.system('security unlock -p "pwd" ~/Library/Keychains/login.keychain') |
来解锁。
可是错误依旧存在
搜了搜,可以强制导入某程序可用的 key, 用 security import
后加参数 -T
1 | os.system('security import login.keychain -P "pwd" -T /usr/bin/codesign') |
依然不行。我差点就放弃了。可是我看到了这篇回复。
回复中说:
我通过添加
SessionCreate
这个 key 到org.jenkins-ci.plist
中去解决了这个问题。
抱着试一试的心态,我也添加了。终于,成功了。
最后,我的 plist 是这样的
1 |
|
可以看到,我使用了 EnvironmentVariables
来添加环境变量。这个效果,可以等同于在 supervisord.conf
文件中的 [supervisord]
段添加 environment=ANDROID_NDK_DIR="/Users/farmbuilder/android/android-ndk-r9d", ANDROID_TOOLS_DIR ="/Users/farmbuilder/android/android-sdks/tools"
。
那么,SessionCreate
是什么东西?
在官方文档中,有这么几行字
sshd is a launchd daemon with the SessionCreate property set, which means that it runs in its own bootstrap namespace. Once a user logs in, the launchd PAM session module (
/usr/lib/pam/pam_launchd.so
, as configured by /etc/pam.d/sshd) moves the bootstrap namespace to within the appropriate per-user bootstrap namespace.Uses the global bootstrap namespace unless the SessionCreate property is specified in the property list file, in which case the daemon runs in its own per-session bootstrap namespace.
Uses the global security context unless the SessionCreate property is specified in the property list file, in which case the daemon runs in its own per-session security context
原来,mac 系统没用户登录时创建的 namespace 是叫做一个 global bootstrap,当有用户登录时,sshd 就会启动,sshd 会创建用户自己的 namespace, 并替代 global bootstrap 成为当前的 namespace(security context 安全内容也如此,包含我们用到的 codesign)。由于我们是创建的 launchd daemon
,如果不添加 SessionCreate
,那么默认是会用 global bootstrap 的。这个 namespace,是肯定不会有我们的 security context 以及 namespace 的。
至此,我们的目标都实现了! :)
OS X 下让 supervisor 开机启动,以及权限、环境变量、codesign 问题
https://robinxb.com/posts/2015/osx-supervisor-python-permission-problem-autostart/