macOS 通过启动硬盘指定目录下的配置文件,来完成启动任务。这些文件为plist,本质上是XML。
0x00 Launchd 目录配置
Mac下Launchd的plist文件放置的目录有
由用户自己定义的任务项
~/Library/LaunchAgents
由管理员为用户定义的任务项
/Library/LaunchAgents
由管理员定义的守护进程任务项
/Library/LaunchDaemons
由 macOS 为用户定义的任务项
/System/Library/LaunchAgents
由 macOS 定义的守护进程任务项
/System/Library/LaunchDaemons
其中 LaunchDaemons 和 LaunchAgents 的区别是什么?
- LaunchDaemons 是用户未登陆前就启动的服务(守护进程)
- LaunchAgents 是用户登陆后启动的服务(守护进程)
0x01 Launchd Plist 配置
标签 | 必填 | 说明 |
---|---|---|
Label | 是 | 标识符,用来表示该任务的唯一性 |
Program | 是 | 程序名称,用来说明运行哪个程序、脚本 |
ProgramArguments | 是 | 同上,与Program二选一或一起使用,只是可以运行多个程序、可带参数 |
WatchPaths | 否 | 监控路径,当路径文件有变化是运行程序,也是数组 |
RunAtLoad | 否 | 是否在加载的同时启动 |
StartCalendarInterval | 否 | 运行的时间,单个时间点使用dict,多个时间点使用 array -> dict |
StartInterval | 否 | 时间间隔,与StartCalendarInterval使用其一,单位为秒 |
StandardInPath、StandardOutPath、StandardErrorPath | 否 | 标准的输入输出错误文件,这里建议不要使用.log作为后缀,会打不开里面的信息 |
两种指定要执行命令的方法:
使用 Program 和 ProgramArguments
- Program, 运行命令或要执行文件路径
- ProgramArguments, 执行时传入参数
只使用 ProgramArguments
- ProgramArguments 的每个参数为要执行的命令或文件路径,其它参数为传入参数
两种设置执行时间的方法:
- StartCalendarInterval 使用元素Minute, Hour, Day, Month, Weekday指定执行时间,如:
<!-- 每天的9:30执行 -->
<key>StartCalendarInterval</key>
<dict>
<key>Minute</key>
<integer>30</integer>
<key>Hour</key>
<integer>9</integer>
</dict>
- StartInterval 设置执行的时间间隔,单位为秒
<!-- 每小时执行一次 -->
<key>StartInterval</key>
<integer>3600</integer>
0x02 Launchd 定时任务
编写我的 shell 脚本文件
功能是在 macOS 开机后自动执行下面的脚本, 开启内网穿透的功能, 可以忽略我的脚本内容, 替换成你想用的脚本
# launch.sh
#! /bin/bash
export ZWY_HOME=/Users/mb
export ZWY_DIR=$ZWY_HOME/ZwyShell
export ZWY_DIR_FeiGeNAT=$ZWY_HOME/ZwyShell/darwin_amd64_client
export PATH=$PATH:$ZWY_DIR:$ZWY_DIR_FeiGeNAT
LOG_PATH=$ZWY_DIR/launch/launch.log
echo "====start zwy launch====" >> $LOG_PATH
echo $(date +"%Y-%m-%d %H:%M:%S") >> $LOG_PATH
echo "npc is in "`which npc` >> $LOG_PATH
npc -server=nps.xxxx.press:8024 -vkey=zwy -type=tcp > $ZWY_DIR_FeiGeNAT/npc_home.log
注意:
可以使用 chmod a+x launch.sh
给权限变成可执行文件, 也可以用 bash 命令调用
编写 Plist 文件
在 ~/Downloads
下面创建一个 Plist 文件, 都写好后, 再复制到想要的权限文件夹下面
<?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.zwy.home</string>
<key>ProgramArguments</key>
<array>
<string>bash</string>
<string>/Users/mb/ZwyShell/launch/launch.sh</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>Version</key>
<string>3</string>
</dict>
</plist>
可以用 plutil -lint com.zwy.home.plist
来验证 Plist 格式是否正确
加载命令
launchctl
是一个统一的服务管理框架,启动、停止和管理守护进程、应用程序、进程和脚本
# 加载任务, -w选项会将plist文件中无效的key覆盖掉,建议加上
launchctl load -w com.zwy.home.plist
launchctl start com.zwy.home
# 删除任务
launchctl stop com.zwy.home.plist
launchctl unload -w com.zwy.home
# 查看任务列表, 使用 grep '任务部分名字' 过滤
launchctl list | grep 'com.zwy.home.plist'
经验
如果执行 launchctl load
命令出现 com.zwy.home.plist: Path had bad ownership/permissions
,需要为 plist 文件赋予600 权限或者 root 权限, 都可以尝试一下:
sudo chmod 600 path/com.zwy.home.plist
sudo chown root:wheel path/com.zwy.home.plist
执行 launchctl list
查看到你的执行结果, 其他错误可以用 launchctl error <insert numerical error code here>
来查看具体错误:
$ launchctl list | grep 'com.zwy.home'
- 78 com.zwy.home # 错误结果
- 0 com.zwy.home # 正确结果
执行 launchctl list
具体描述如下:
1230 - com.apple.speech.speechsynthesisd
353 - com.apple.security.cloudkeychainproxy3
255 - com.apple.secd
- 0 com.apple.sbd
第一列表示进程号,如果有在结果中罗列,但没有数字而只是一个横线,标志虽然已经loaded, 但没有运行
第二列是上次退出的状态号(the last exit code), 0表示成功,正数表示错误退出,负数表示收到信号后退出
如果 shell脚本输出如果有中文,中文部分乱码,则需要在shell脚本中指定编码:
#!/bin/bash
LANG=en_US.UTF-8
export LANG
echo "你好贾维斯"
2024-07-10 补充
有时候会遇到 exit code 为 1, 标识没有权限, 在我的这里, 实际上是 bash 没有权限访问磁盘文件, 在系统设置里添加自己的 bash 为完全磁盘访问权限即可, 查看 bash 在哪里, 用which bash