You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

macOS屏幕锁定时launchDaemon无法运行脚本的解决求助

解决受管理macOS设备上LaunchDaemon锁屏后不运行的问题

先解决日志无输出问题(核心排查基础)

没有日志就无法定位问题,先给监控LaunchDaemon添加日志配置:

  1. 在plist中加入日志路径配置:
    <key>StandardOutPath</key>
    <string>/var/log/admin_monitor.log</string>
    <key>StandardErrorPath</key>
    <string>/var/log/admin_monitor_error.log</string>
    
  2. 给日志文件设置正确权限,避免写入失败:
    sudo chown root:wheel /var/log/admin_monitor*.log
    
  3. 重启LaunchDaemon后锁屏测试,查看日志内容:
    sudo launchctl unload /Library/LaunchDaemons/com.yourcompany.adminmonitor.plist
    sudo launchctl load /Library/LaunchDaemons/com.yourcompany.adminmonitor.plist
    

检查LaunchDaemon配置合法性

受管理设备对LaunchDaemon配置要求严格,确保以下几点:

  • plist必须放在/Library/LaunchDaemons/目录,文件名采用反向域名格式(例如com.yourcompany.adminmonitor.plist)
  • plist权限必须为root:wheel 644,执行命令修正:
    sudo chmod 644 /Library/LaunchDaemons/com.yourcompany.adminmonitor.plist
    sudo chown root:wheel /Library/LaunchDaemons/com.yourcompany.adminmonitor.plist
    
  • 配置正确的运行触发规则:如果是持续监控,用StartInterval定时检查(例如每分钟一次),不要依赖用户会话相关触发条件:
    <key>RunAtLoad</key>
    <true/>
    <key>StartInterval</key>
    <integer>60</integer>
    

修正脚本的用户上下文依赖问题

锁屏后用户会话会切换,脚本中如果使用依赖当前登录用户的命令(如consoleuser、who)会失效,需直接从系统数据库读取用户信息:

  • 用dscl命令获取所有非系统账户(UniqueID ≥500),并检查管理员组:
    # 获取非系统用户列表
    users=$(dscl . -list /Users UniqueID | awk '$2 >= 500 {print $1}')
    for user in $users; do
        # 跳过系统服务账户(以_开头)
        [[ "$user" =~ ^_ ]] && continue
        # 检查是否在admin组
        if dscl . -read /Groups/admin GroupMembership | grep -q "$user"; then
            # 执行创建定时移除Daemon的逻辑
            create_remove_daemon "$user"
        fi
    done
    

定时移除Daemon的正确配置

创建的移除权限Daemon要确保一次性运行、不依赖用户会话:

  1. 模板plist设置KeepAlive为false,OnDemand为true,避免重复运行
  2. 用StartInterval设置900秒(15分钟)延迟,不要在脚本中使用sleep(sleep会占用进程,可能被系统回收)
  3. 同样给移除Daemon添加日志配置,方便排查执行情况

排查MDM限制

受管理设备可能存在MDM策略限制:

  • 联系MDM管理员确认是否有“禁止后台进程锁屏时运行”的策略
  • 确保脚本和LaunchDaemon路径在SIP允许范围内(/Library/LaunchDaemons/和/Library/Scripts/均为合法路径)

示例完整配置

监控LaunchDaemon plist(/Library/LaunchDaemons/com.yourcompany.adminmonitor.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.yourcompany.adminmonitor</string>
    <key>ProgramArguments</key>
    <array>
        <string>/Library/Scripts/admin_monitor.sh</string>
    </array>
    <key>RunAtLoad</key>
    <true/>
    <key>StartInterval</key>
    <integer>60</integer>
    <key>StandardOutPath</key>
    <string>/var/log/admin_monitor.log</string>
    <key>StandardErrorPath</key>
    <string>/var/log/admin_monitor_error.log</string>
    <key>User</key>
    <string>root</string>
    <key>GroupName</key>
    <string>wheel</string>
</dict>
</plist>

监控脚本(/Library/Scripts/admin_monitor.sh)

#!/bin/bash

LOG_FILE="/var/log/admin_monitor.log"
REMOVE_TEMPLATE="/Library/Scripts/remove_admin_template.plist"

log() {
    echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" >> "$LOG_FILE"
}

create_remove_daemon() {
    local user="$1"
    local daemon_label="com.yourcompany.removeadmin.$user"
    local daemon_path="/Library/LaunchDaemons/$daemon_label.plist"

    [ -f "$daemon_path" ] && { log "Remove daemon for $user exists, skip"; return; }

    cp "$REMOVE_TEMPLATE" "$daemon_path"
    sed -i '' "s/{{USER}}/$user/g" "$daemon_path"
    sed -i '' "s/{{LABEL}}/$daemon_label/g" "$daemon_path"

    chown root:wheel "$daemon_path"
    chmod 644 "$daemon_path"

    launchctl load "$daemon_path"
    log "Created remove daemon for $user"
}

check_admin_users() {
    log "Starting admin check"
    users=$(dscl . -list /Users UniqueID | awk '$2 >= 500 {print $1}')
    for user in $users; do
        [[ "$user" =~ ^_ ]] && continue
        if dscl . -read /Groups/admin GroupMembership | grep -q "$user"; then
            log "Found admin user: $user"
            create_remove_daemon "$user"
        else
            local daemon_path="/Library/LaunchDaemons/com.yourcompany.removeadmin.$user.plist"
            [ -f "$daemon_path" ] && { launchctl unload "$daemon_path"; rm "$daemon_path"; log "Removed daemon for $user"; }
        fi
    done
    log "Admin check finished"
}

check_admin_users

移除权限Daemon模板(/Library/Scripts/remove_admin_template.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>{{LABEL}}</string>
    <key>ProgramArguments</key>
    <array>
        <string>/usr/sbin/dseditgroup</string>
        <string>-o</string>
        <string>edit</string>
        <string>-d</string>
        <string>{{USER}}</string>
        <string>-t</string>
        <string>user</string>
        <string>admin</string>
    </array>
    <key>RunAtLoad</key>
    <true/>
    <key>StartInterval</key>
    <integer>900</integer>
    <key>StandardOutPath</key>
    <string>/var/log/remove_admin_{{USER}}.log</string>
    <key>StandardErrorPath</key>
    <string>/var/log/remove_admin_{{USER}}_error.log</string>
    <key>User</key>
    <string>root</string>
    <key>GroupName</key>
    <string>wheel</string>
    <key>KeepAlive</key>
    <false/>
    <key>OnDemand</key>
    <true/>
</dict>
</plist>

内容的提问来源于stack exchange,提问作者meatballbeam

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.10 00:02:04