macOS에서 launchd로 파이썬 스크립트 부팅 시 자동 실행하는 방법

맥을 켤 때마다 매번 터미널을 열어서 파이썬 스크립트를 실행하는 게 번거로우셨나요. launchd를 활용하면 시스템 부팅이나 잠자기 해제 시 자동으로 스크립트가 실행되도록 설정할 수 있어요. 윈도우의 작업 스케줄러처럼 macOS에는 launchd라는 강력한 자동화 도구가 내장되어 있거든요.


파이썬 로고 - 파란색과 노란색 뱀이 십자 형태로 얽힌 심볼


launchd plist 파일 작성하기


먼저 자동 실행할 파이썬 스크립트를 준비해요. 예를 들어 /Users/username/scripts/monitor.py 라는 시스템 모니터링 스크립트가 있다고 가정해볼게요.

#!/usr/bin/env python3
# monitor.py - 시스템 상태를 체크하는 스크립트
import datetime
import psutil

with open('/tmp/system_log.txt', 'a') as f:
    f.write(f"{datetime.datetime.now()} - CPU: {psutil.cpu_percent()}%\n")


이제 핵심인 plist 파일을 작성해요. ~/Library/LaunchAgents/ 디렉토리에 com.myname.monitor.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.myname.monitor</string>
    
    <key>ProgramArguments</key>
    <array>
        <string>/usr/bin/python3</string>  <!-- which python3로 확인 -->
        <string>/Users/username/scripts/monitor.py</string>
    </array>
    
    <key>RunAtLoad</key>
    <true/>  <!-- 부팅/로그인 시 즉시 실행 -->
    
    <key>StartInterval</key>
    <integer>300</integer>  <!-- 5분마다 반복 실행 (선택사항) -->
    
    <key>StandardOutPath</key>
    <string>/tmp/monitor.out.log</string>
    
    <key>StandardErrorPath</key>
    <string>/tmp/monitor.err.log</string>
    
    <key>WorkingDirectory</key>
    <string>/Users/username/scripts</string>  <!-- 작업 디렉토리 지정 -->
</dict>
</plist>


잠자기 해제 시에도 실행되게 하려면


맥이 잠자기 상태에서 깨어날 때도 스크립트를 실행하고 싶다면 StartOnWake 키를 추가해요. 다만 최신 macOS 버전에서는 이 옵션이 완벽하게 동작하지 않을 수 있어요.

<key>StartOnWake</key>
<true/>  <!-- 잠자기 해제 시 실행 -->

<!-- 더 안정적인 방법: KeepAlive 사용 -->
<key>KeepAlive</key>
<dict>
    <key>OtherJobEnabled</key>
    <true/>
</dict>


실제로 많은 개발자들이 StartOnWake 대신 KeepAlive와 StartInterval을 조합해서 사용해요. 이렇게 하면 시스템이 깨어났을 때 놓친 실행 주기를 자동으로 처리하거든요.


가상환경을 사용하는 경우의 해결책


conda나 pipenv 같은 가상환경을 쓴다면 직접 파이썬을 호출하는 대신 쉘 스크립트를 거쳐야 해요. launchd는 가상환경 활성화 명령을 직접 실행할 수 없거든요.

#!/bin/bash
# run_with_conda.sh - 가상환경 활성화 후 스크립트 실행

# conda 환경 설정
source ~/anaconda3/etc/profile.d/conda.sh
conda activate myproject

# 환경변수 설정
export PATH="/usr/local/bin:$PATH"
export PYTHONPATH="/Users/username/myproject:$PYTHONPATH"

# 실제 스크립트 실행
python3 /Users/username/scripts/monitor.py >> /tmp/conda_run.log 2>&1


그리고 plist에서는 이 쉘 스크립트를 호출하도록 변경해요.

<key>ProgramArguments</key>
<array>
    <string>/bin/bash</string>
    <string>/Users/username/scripts/run_with_conda.sh</string>
</array>


실행 권한 설정과 로드하기


스크립트에 실행 권한을 부여하는 걸 잊으면 안 돼요. 이거 놓치면 로그에 permission denied 에러만 잔뜩 쌓여요.

# 파이썬 스크립트와 쉘 스크립트 모두 실행 권한 부여
chmod +x /Users/username/scripts/monitor.py
chmod +x /Users/username/scripts/run_with_conda.sh

# plist 파일 권한은 644로 설정
chmod 644 ~/Library/LaunchAgents/com.myname.monitor.plist


이제 launchctl로 plist를 등록해요.

# plist 로드 (즉시 실행 시작)
launchctl load -w ~/Library/LaunchAgents/com.myname.monitor.plist

# 상태 확인
launchctl list | grep com.myname.monitor

# 문제가 있다면 언로드 후 다시 로드
launchctl unload -w ~/Library/LaunchAgents/com.myname.monitor.plist
launchctl load -w ~/Library/LaunchAgents/com.myname.monitor.plist


자주 발생하는 문제와 해결 방법


경로 문제가 가장 흔해요. which python3 명령으로 정확한 파이썬 경로를 확인하고, 스크립트 경로도 절대경로로 지정해야 해요. 상대경로 쓰면 십중팔구 실패해요.

# 파이썬 경로 확인
which python3  # 보통 /usr/bin/python3 또는 /usr/local/bin/python3

# 로그 파일로 문제 진단
tail -f /tmp/monitor.err.log  # 에러 로그 실시간 확인


환경변수 문제도 빈번해요. launchd는 터미널과 다른 환경에서 실행되기 때문에 PATH나 PYTHONPATH가 설정되어 있지 않아요. 필요한 환경변수는 plist에 직접 추가하거나 쉘 스크립트에서 설정해요.

<!-- plist에서 환경변수 설정하는 방법 -->
<key>EnvironmentVariables</key>
<dict>
    <key>PATH</key>
    <string>/usr/local/bin:/usr/bin:/bin</string>
    <key>PYTHONPATH</key>
    <string>/Users/username/myproject</string>
</dict>


최신 macOS(Ventura 이상)에서는 보안 정책이 더 엄격해졌어요. 시스템 환경설정의 "보안 및 개인정보 보호"에서 터미널이나 자동화 도구에 "전체 디스크 접근" 권한을 줘야 할 수도 있어요.


특정 시간에만 실행하고 싶다면


매일 오전 9시에만 실행하고 싶다면 StartCalendarInterval을 사용해요.

<key>StartCalendarInterval</key>
<dict>
    <key>Hour</key>
    <integer>9</integer>
    <key>Minute</key>
    <integer>0</integer>
</dict>


여러 시간대에 실행하려면 배열로 지정할 수도 있어요.

<key>StartCalendarInterval</key>
<array>
    <dict>
        <key>Hour</key>
        <integer>9</integer>
    </dict>
    <dict>
        <key>Hour</key>
        <integer>18</integer>
    </dict>
</array>


디버깅 팁과 실전 노하우


launchctl이 상태 코드 78을 반환한다면 대부분 경로나 권한 문제예요. Desktop이나 Downloads 폴더에 스크립트를 두면 권한 문제가 자주 발생하니까 홈 디렉토리 하위의 전용 폴더를 만들어 사용하는 게 좋아요.

# 추천하는 디렉토리 구조
mkdir -p ~/scripts/python
mkdir -p ~/scripts/logs
mkdir -p ~/Library/LaunchAgents


로그 파일은 꼭 설정해두세요. 자동 실행이 실패할 때 원인을 찾는 유일한 단서가 되거든요. StandardOutPath와 StandardErrorPath를 둘 다 지정하면 정상 출력과 에러를 분리해서 확인할 수 있어요.


plist 파일 수정 후에는 반드시 unload하고 다시 load해야 변경사항이 적용돼요. 그냥 수정만 하면 이전 설정대로 계속 실행되니까 주의하세요.


여러 개의 스크립트를 관리한다면 Label을 체계적으로 지정하는 게 중요해요. com.회사명.프로젝트명.기능명 형식으로 만들면 나중에 관리하기 편해요.


macOS Raycast에 GPT-5 Extension 설치하고 고급 스크립트로 확장하는 방법