"한 호스트에서 한 순간에 한 번 찔러본 결과는 찔러본 그 행동에 대한 증거일 뿐이야."
사용자는 하나, 맥락은 셋
Mac 화면에서 로그인하면 macOS가 너한테 Aqua 세션을 만들어줘. 여기엔 윈도 서버, 권한 창을 띄울 수 있는 로그인 키체인, gui/<uid> launchd 도메인이 딸려 와. 같은 사용자로 ssh 로그인하면 파일은 같은데 보안 세션은 달라. launchd 작업은 또 다른 세 번째 맥락이고, 환경도 따로야. 여러 Mac을 다루는 도구가 지금 어느 맥락에서 도는지 까먹으면 앱 버그처럼 보이는 에러가 나와.
ssh 세션이 할 수 있는 것과 없는 것
주인이 콘솔에 로그인해 있는 Mac에 ssh로 들어가서 잰 결과야.
- 로그인 키체인이 "User interaction is not allowed"라고 대답해. 그래서 거기 든 신원으로
codesign하면 속을 알 수 없는errSecInternalComponent로 실패해. 자체 서명 신원도, iOS 아카이브에 필요한 Apple Development 신원도 마찬가지야. launchctl asuser는 "Could not switch to audit session … Operation not permitted"로 실패해. 세션을 바꾸려면 root가 필요하거든.- 시그널은 평소처럼 돼. 앱에 직접 보내는 Apple Event(
tell application "Spark" to quit)도 됐어. 근데 System Events를 거치는 건 에러 -1712가 날 때까지 멈춰 있었어.osascript는 타임아웃을 걸고 백그라운드로 돌려. 저장하지 않은 변경 사항 대화상자에 막힌 종료 명령은 영영 안 돌아오거든.
open은 결론을 느슨하게 잡아둬야 하는 경우야. 7월에 어느 Mac에서 찔러봤을 땐 exit 0인데 아무것도 안 떴어. 9월에 또 다른 Mac에서 찔러봤을 땐 AppKit 앱이 떠서 applicationDidFinishLaunching까지 갔어. 두 측정이 서로 어긋나니까 어느 쪽도 법칙으로 삼으면 안 돼. 둘이 함께 증명하는 건 종료 상태만으론 아무것도 알 수 없다는 거야. 그러니 프로세스가 있는지 직접 확인해.
ssh에서 GUI 세션 일 하기
믿을 만한 다리는 gui/<uid>에 bootstrap하는 일회용 LaunchAgent야. 작업이 Aqua 세션 안에서 도니까 키체인을 쓸 수 있고 서명도 성공해. 창도 안 떠. 가족 iOS 아카이브가 원격 세션에서 이렇게 돌아. AppleScript로 터미널한테 스크립트를 돌리라고 시키는 방법도 끌리지만, 그렇게 했더니 맨 앞에 있던 탭에 경로를 쳐 넣었어. 그 탭은 주인이 보고 있던 시스템 모니터였어. 사람의 세션에 닿으려고 그 사람 앱을 조종하지 마.
이 다리는 콘솔에 로그인한 사용자가 있어야 통해. 로그인 창에 멈춰 있으면 GUI 도메인이 아예 없어서 launchctl bootstrap gui/<uid>가 125: Domain does not support specified action으로 실패해. 상시 에이전트라면 괜찮아. plist를 ~/Library/LaunchAgents에 넣어두면 그걸로 영구히 활성화된 거고, 다음 로그인 때 RunAtLoad가 켜주거든. 실패라고 하지 말고 대기 중이라고 보고해. 일회용 작업은 기다릴 다음 로그인이 없으니, 똑같이 콘솔 사용자가 없어도 그땐 이름을 대고 실패해야 해.
launchd 작업은 네 셸이 아니야
작업의 PATH는 plist가 따로 안 정하면 launchd 기본값인 /usr/bin:/bin:/usr/sbin:/sbin이라서 Homebrew 도구를 못 찾아. Homebrew를 넣으려고 plist에 PATH를 정하다가 /usr/sbin을 빼먹을 수도 있어. 어떤 엔진의 lsof 검사가 모든 포트에 대해 듣고 있는 프로세스가 없다고 보고한 게 딱 그 경우였어. 로컬 네트워크 개인정보 보호도 달라. 터미널이나 ssh로 돌린 도구는 자동으로 허용되는데 launchd 에이전트는 아니야. 에이전트로 돌던 엔진은 LAN 호스트 이름으로 접속하다 "No route to host"를 받았는데, 같은 사용자의 ssh 셸에서는 잘 접속됐어.