launchd는 macOS의 서비스 관리자(service manager)다(리눅스의 systemd에 해당). ~/Library/LaunchAgents에 plist(property list)를 두면 로그인할 때 프로세스를 띄우고, 죽으면 다시 살린다.
plist 핵심 키
<key>Label</key><string>com.example.relay</string>
<key>ProgramArguments</key>
<array><string>/usr/local/bin/myserver</string><string>--port</string><string>8080</string></array>
<key>WorkingDirectory</key><string>/path/to/app</string>
<key>EnvironmentVariables</key>
<dict><key>PATH</key><string>/usr/local/bin:/usr/bin:/bin</string></dict>
<key>RunAtLoad</key><true/>
<key>KeepAlive</key><true/>
<key>StandardOutPath</key><string>/tmp/myserver.log</string>
<key>StandardErrorPath</key><string>/tmp/myserver.err</string>- 실행 파일은 절대 경로로 쓴다. launchd는 로그인 셸 설정을 읽지 않아
PATH가 최소한으로만 잡혀 있다(환경변수 스코프와 source) RunAtLoad는 로드 즉시 실행,KeepAlive는 종료되면 재시작이다- 비밀값을 plist에 넣으면 파일 권한을 좁히고, 저장소에 올리지 않는다
다루는 명령
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.example.relay.plist # 로드
launchctl print gui/$(id -u)/com.example.relay | grep -E "state|pid" # 상태
launchctl kickstart -k gui/$(id -u)/com.example.relay # 재시작
launchctl bootout gui/$(id -u)/com.example.relay # 언로드함정: plist를 고쳤는데 반영이 안 된다
kickstart -k는 이미 로드된 job 정의로 프로세스만 다시 띄운다. plist의 EnvironmentVariables를 바꿨다면 bootout으로 내린 뒤 bootstrap으로 다시 올려야 한다. bootout이 끝나기 전에 바로 bootstrap하면 "I/O error"로 실패할 수 있어, 잠깐 기다리거나 상태를 확인한 뒤 올린다. 실행 중인 프로세스가 실제로 받은 환경은 ps eww <pid>로 확인할 수 있다.
로그 파일을 보고도 원인이 안 보이면, 프로세스 안에서 예외를 삼키고 있지 않은지 의심한다(asyncio 태스크 예외가 조용히 사라지는 문제, 예외 삼키기). 포트가 계속 점유돼 있다면 KeepAlive가 되살리는 중일 수 있다(포트를 쓰는 프로세스 찾아 종료하기).
출처: man launchd.plist, man launchctl · Apple 문서 보관소: Creating Launch Daemons and Agents