잠들지 않는 맥북 만들기#

2026-11-01

저녁 통근 열차 좌석 위에 덮개를 닫은 노트북이 놓여 있고, 닫힌 틈으로 따뜻한 빛이 새어 나온다. 옆에는 모래가 흘러내리는 모래시계와 김이 오르는 커피잔이 있고, 노트북에서 이어진 빛줄기가 맞은편 사람이 든 휴대폰으로 닿는 그림

멈추는 게 당연했던 이동#

필자는 긴 작업을 돌릴 때 전원을 연결하고 덮개를 연 채 터미널에 caffeinate를 띄워 둡니다. Mac이 잠들어 작업이 끊기는 것을 막는 macOS 기본 명령입니다. 다만 쓰임새는 거기까지였습니다. 자리를 옮겨야 하면 덮개를 닫았고, 그동안 작업이 멈추는 것은 당연한 일로 여겼습니다.

지난 글에서 휴대폰으로 노트북의 코딩 에이전트 세션을 들여다보는 방법을 정리할 때도, “덮개를 닫으면 caffeinate로도 막을 수 없습니다"라고 한 줄 적고 넘어갔습니다. 그런데 휴대폰으로 세션을 볼 수 있게 되자 질문이 하나 생겼습니다. 화면을 들고 다닐 수 있다면, 노트북은 덮개를 닫아 가방에 넣고 이동하는 동안에도 계속 일하게 할 수 있지 않을까요?

가능했습니다. 명령 한 줄이면 됩니다. 시간이 든 곳은 그다음이었습니다. 잠들지 않게 하는 것은 한 줄이지만, 그 상태를 켜 둔 채 잊어도 괜찮게 만드는 데는 설계가 필요했습니다. 이 글은 그 한 줄에서 출발해 작은 도구를 만들어 공개하기까지의 기록입니다.

caffeinate가 막는 것과 막지 못하는 것#

먼저 caffeinate가 어디까지 막는지 확인했습니다. 자주 쓰는 옵션은 두 가지입니다.

  • -i: 입력이 없어서 Mac이 저절로 잠드는 유휴 잠자기를 막습니다.
  • -s: 시스템 잠자기를 막습니다. man 페이지에 따르면 전원이 연결돼 있을 때만 유효합니다.

caffeinate는 macOS 전원 관리에 “잠들지 마"라는 요청(assertion)을 걸어 두는 방식으로 동작합니다. Apple의 IOKit 전원 관리 문서는 -i가 거는 요청에 대해 분명히 적어 두었습니다. 이 요청이 걸려 있어도 덮개를 닫거나, Apple 메뉴에서 잠자기를 고르거나, 배터리가 부족하면 시스템은 잠들 수 있다고요. -s가 거는 요청에 대해서는 같은 문서에 덮개를 닫을 때의 동작이 나와 있지 않습니다. 문서로 확인되지 않는 동작에는 기대지 않기로 했습니다.

정리하면 caffeinate는 “가만히 둬도 잠들지 않게” 하는 도구이지, “사람이 재우려 해도 버티게” 하는 도구가 아닙니다. 직접 잠자기를 고르면 Mac은 실제로 잠들고, 그 위에서 돌던 작업도 함께 멈춥니다. 덮개를 닫는 것도 마찬가지입니다.

disablesleep: 덮개를 이기는 대신 설정이 남는다#

덮개를 닫아도 잠들지 않게 하는 방법은 pmset에 있었습니다. pmset은 macOS의 전원 관리 설정을 바꾸는 명령입니다.

sudo pmset -a disablesleep 1   # 잠자기 비활성화
sudo pmset -a disablesleep 0   # 원래대로

이 설정을 켜고 덮개를 닫으면 Mac은 잠들지 않습니다. 처음 써 보고 의외였던 점은 덮개를 닫아도 화면이 꺼지지 않는다는 것이었습니다. 그래서 pmset displaysleepnow로 화면을 먼저 끈 뒤 덮개를 닫아야 합니다.

더 중요한 차이는 따로 있었습니다. caffeinate는 실행 중인 프로세스가 거는 요청이라 프로세스가 끝나면 함께 사라집니다. 반면 disablesleep은 시스템 설정 자체를 바꿉니다. 값이 전원 관리 설정 파일(/Library/Preferences/com.apple.PowerManagement.plist)에 저장되기 때문에, 명령이 끝나도 재부팅을 해도 남습니다.

caffeinatedisablesleep
덮개를 닫으면잠듦잠들지 않음
풀리는 시점프로세스가 끝날 때직접 끌 때까지
재부팅 후사라짐남음
관리자 권한(sudo)필요 없음필요

caffeinate는 끄는 것을 잊어도 손해가 거의 없습니다. disablesleep은 끄는 것을 잊으면, 며칠 뒤 가방에 넣을 때도 Mac은 잠들지 않습니다. 그래서 두 방법은 하나를 고르는 관계가 아니라 쓰임이 나뉩니다. 덮개를 열어 둘 수 있으면 caffeinate, 닫아야 할 때만 disablesleep입니다.

잊어도 괜찮게 만들기#

처음에는 켜고 끄는 셸 함수 두 개(awake, unawake)를 만들었습니다. 하지만 함수가 있다고 끄는 것을 잊지 않게 되지는 않습니다. 그래서 요구사항을 세 가지로 정리했습니다.

  1. 켜져 있는 동안 메뉴 막대에 계속 보일 것. 메뉴에서 바로 끌 수도 있을 것
  2. 정해 둔 최대 시간이 지나면 저절로 꺼질 것
  3. 꺼져 있을 때는 불필요한 감시를 하지 않을 것

정리하고 보니 1번과 3번이 부딪혔습니다. 켜져 있을 때만 표시하려면 켜졌는지 알아야 하고, 그걸 알려면 누군가 주기적으로 확인해야 합니다. 그 확인이 곧 감시입니다.

해결은 확인하는 쪽을 바꾸는 것이었습니다. 메뉴 막대 앱을 늘 띄워 두는 대신, 설정을 켜는 awake가 앱을 직접 띄웁니다. 앱은 켜져 있는 동안만 몇 초마다 상태를 확인하며 남은 시간을 보여 주고, 설정이 꺼지면 스스로 종료합니다. 꺼져 있을 때는 돌고 있는 것이 하나도 없습니다. 2번의 자동 해제는 뒤에서 다룰 타이머가 맡습니다.

구멍이 하나 남았습니다. 타이머가 돌던 중에 강제 종료나 재부팅이 일어나면 타이머는 사라지고 설정만 남습니다. 이 경우를 위해 로그인할 때 앱을 딱 한 번 띄우게 했습니다. 설정이 꺼져 있으면 앱은 곧바로 종료하고, 켜져 있으면 메뉴 막대에 ☕️를 띄웁니다. 타이머 없이 설정만 남는 일은 주로 재부팅 뒤에 생기므로, 계속 지켜보는 대신 그 시점에 한 번만 확인하는 셈입니다.

설치할까, 만들까#

메뉴 막대에 셸 스크립트 결과를 띄워 주는 앱으로 SwiftBar가 있습니다. 처음에는 이 앱을 쓰려고 했고, 들이기 전에 안전한지부터 확인했습니다.

  • 공식 GitHub 릴리스에서 최신 버전(2.1.1)을 받아 서명을 검사했습니다. Apple이 발급한 개발자 인증서로 서명돼 있었고, Apple 공증(notarization, Apple의 악성코드 검사를 통과했다는 표시)도 붙어 있었습니다.
  • MIT 라이선스 오픈소스이고, 2026-11-01 기준으로 GitHub 별이 4,700여 개입니다. macOS 패키지 관리자인 Homebrew로 설치된 횟수는 최근 1년 동안 3만 건을 넘습니다. 저장소에 등록된 보안 공지는 없었습니다.

충분히 쓸 만한 앱이었습니다. 그런데 필요한 기능을 다시 세어 보니 표시 하나와 메뉴 하나였습니다. 이 정도라면 남이 만든 앱 전체를 들이는 것보다 필자가 전부 아는 코드를 두는 편이 낫다고 판단했습니다. 코딩 에이전트와 함께 만들면 그만큼을 만드는 수고가 앱을 설치하고 설정하는 수고와 크게 다르지 않다는 점도 컸습니다. 실제로 만들어 보니 메뉴 막대 앱이 100줄, 스크립트까지 합쳐도 200줄 남짓이었습니다.

기준은 규모입니다. 메뉴 막대에 띄울 것이 VPN 상태, 빌드 상태처럼 여러 개로 늘어난다면 스크립트만 추가하면 되는 SwiftBar가 훨씬 낫습니다. PR 에이전트 글에서 제품을 들이는 대신 설계를 가져왔던 것과 비슷한 결론이지만, 이번에는 이유가 더 단순했습니다. 필요한 것이 작았습니다.

이전 타이머가 새 설정을 끄는 문제#

자동 해제 타이머를 어디서 돌릴지도 정해야 했습니다. 하나는 awake가 끝나지 않고 터미널에 남아 있다가, 시간이 다 되거나 Ctrl+C를 누르면 설정을 끄는 방식입니다. 다른 하나는 awake가 바로 끝나고 타이머는 뒤에서 도는 방식입니다. 메뉴 막대가 남은 시간과 끄는 버튼을 보여 주니 터미널 탭을 붙잡아 둘 이유가 없어서, 뒤에서 도는 방식을 골랐습니다.

그런데 어느 방식이든 같은 함정이 있었습니다. 다른 터미널에서 unawake를 실행하면 설정은 꺼지지만 기존 타이머는 살아 있습니다. 그 상태에서 다시 awake를 실행하면, 이전 타이머가 자기 시간이 됐을 때 새로 켠 설정까지 꺼 버립니다. 덮개를 닫고 작업 중이었다면 예상보다 일찍 잠드는 것입니다.

해결 방법은 “현재 타이머"를 하나만 기록하는 것이었습니다. 상태 파일에 현재 타이머의 프로세스 번호(PID)와 해제 시각을 적어 둡니다. 켜거나 끌 때는 기록을 먼저 지우고 이전 타이머를 종료합니다. 시간이 다 된 타이머는 자기가 아직 현재 타이머일 때만 설정을 끕니다.

STATE=/var/run/sleepguard.state   # "타이머 PID 해제 시각" 한 줄

stop_timer() {
  [ -f "$STATE" ] || return 0
  pid=$(cut -d' ' -f1 "$STATE")
  rm -f "$STATE"              # 기록을 먼저 지우고
  kill "$pid" 2>/dev/null     # 이전 타이머를 종료
}

# 타이머 쪽: 시간이 다 되면, 아직 내가 현재 타이머일 때만 끈다
if [ "$(cut -d' ' -f1 "$STATE" 2>/dev/null)" = "$$" ]; then
  rm -f "$STATE"
  pmset -a disablesleep 0
fi

실제 코드는 종료하기 전에 그 PID가 정말 타이머 프로세스인지도 확인합니다. 프로세스 번호가 다른 프로그램에 재사용됐을 수 있기 때문입니다.

만들고 검증하기#

메뉴 막대 앱은 처음에 Swift로 작성했습니다. 그런데 Xcode 없이 설치하는 개발 도구 모음(Command Line Tools) 안에서 Swift 컴파일러와 SDK(앱이 macOS 기능을 쓸 때 참조하는 라이브러리 묶음)의 버전이 미세하게 달라 빌드가 되지 않았습니다. 도구를 다시 설치하는 대신 같은 기능을 Objective-C로 바꿨고, 같은 도구 모음의 컴파일러로 문제없이 빌드됐습니다. 작은 앱이라 언어를 바꾸는 비용이 도구를 고치는 비용보다 작았습니다.

검증은 사람과 에이전트가 나눠서 했습니다. 이번 작업을 함께 한 코딩 에이전트(Claude Code)는 sudo 비밀번호를 입력할 수 없어서 실제 설정을 바꿀 수 없었습니다. 그래서 에이전트는 진짜 pmset 대신 호출 기록만 남기는 가짜 pmset을 만들어 타이머 로직을 검사했습니다. 시간이 되면 꺼지는지, 다시 켜면 이전 타이머가 정리되는지, 엉뚱한 프로세스를 종료하지 않는지 같은 것들입니다. 가짜 상태로 메뉴 막대 앱을 띄워 화면을 캡처하고 표시도 확인했습니다. 비밀번호를 넣고 실제로 ☕️가 뜨는지, 메뉴로 꺼지는지, 정해 둔 시간 뒤에 저절로 꺼지는지는 필자가 직접 확인했습니다.

반복할 수 있는 로직 검사는 에이전트가, 권한이 필요한 마지막 확인은 사람이 맡은 셈입니다. 가짜 pmset으로 하는 검사는 그대로 저장소의 테스트가 됐습니다.

나만 쓰던 스크립트를 남이 쓸 도구로#

만들고 나니 공유하고 싶어졌습니다. 그런데 공개하려고 다시 보니, 혼자 쓸 때는 보이지 않던 문제들이 나왔습니다. 혼자 쓸 때의 기준이 “내가 잊어도 괜찮은가"였다면, 공개할 때는 “모르는 사람이 써도 괜찮은가"였습니다.

  • 개인 경로: 로그인할 때 앱을 띄우는 설정 파일에 필자의 홈 폴더 경로가 그대로 적혀 있었습니다. 설치 스크립트가 설치하는 사람의 경로로 만들어 넣게 바꿨습니다.
  • root(시스템 최고 관리자 계정)로 도는 스크립트의 위치: 처음에는 홈 폴더 아래, 즉 필자의 계정이 고칠 수 있는 곳에 있었습니다. sudo로 실행되는 파일을 일반 계정이 고칠 수 있으면, 그 계정을 장악한 악성 프로그램이 파일을 바꿔치기해 관리자 권한을 얻을 수 있습니다. root만 고칠 수 있는 /usr/local/libexec 아래로 옮기고, 설치할 때 그 경로를 다른 사용자가 고칠 수 없는지 검사하게 했습니다.
  • 제거 스크립트: 비밀번호 입력에 실패해도 제거가 계속 진행됐습니다. 그러면 설정은 켜진 채 남고, 메뉴 막대 표시와 끄는 명령만 사라집니다. 이 도구가 막으려던 “켜 둔 것을 모르는 상태"를 제거 스크립트가 만드는 셈이었습니다. 실패하면 아무것도 지우지 않고 멈추게 고쳤습니다.
  • 알아볼 수 없는 이름: 시스템 설정의 “백그라운드에서 허용” 목록에 로그인 항목이 open으로만 표시됐습니다. 앱을 open 명령으로 띄우도록 해 둔 탓입니다. 앱 실행 파일을 직접 실행하게 바꿨습니다.
  • 근거보다 강한 문장: 설명서에 “caffeinate는 덮개를 닫으면 못 막는다"고 옵션 구분 없이 적었는데, 문서로 확인되는 것은 -i뿐이었습니다. -s는 “문서에 없음"으로 고쳤습니다.

이렇게 다듬은 결과를 sleep-guard-for-mac으로 공개했습니다. 설치하면 세 가지 명령이 생깁니다.

git clone https://github.com/tedkim81/sleep-guard-for-mac.git
cd sleep-guard-for-mac && ./install.sh

awake          # 3시간 동안 잠자기 방지 + 화면 끄기 (awake 90m, awake 2h처럼 시간 지정 가능)
unawake        # 끄기
screenoff      # 화면만 끄기 (caffeinate와 함께 쓸 때)

이동 중에도 작업이 이어지려면#

잠들지 않는 것만으로는 부족했습니다. 이동하는 동안 작업이 제대로 이어지려면 조건이 두 가지 더 필요합니다.

네트워크#

사무실 Wi-Fi 범위를 벗어나는 순간 노트북의 인터넷이 끊깁니다. 코딩 에이전트는 모델 API를 계속 호출하고, 지난 글의 Remote Control로 휴대폰에서 세션을 보려면 노트북도 인터넷에 연결돼 있어야 합니다. 가장 손쉬운 방법은 휴대폰의 개인용 핫스팟입니다.

Apple 지원 문서에 따르면 macOS Tahoe 26부터는 Wi-Fi 설정의 ‘핫스팟 연결 요청’을 ‘자동’으로 두면, Wi-Fi를 쓸 수 없을 때 근처 iPhone의 핫스팟에 자동으로 연결됩니다. 그 이전 버전에는 ‘자동’ 선택지가 없습니다. 어느 쪽이든 출발하기 전에 핫스팟에 미리 연결해 두는 편이 확실합니다. 이동 중에 네트워크가 바뀌는 순간에는 진행 중이던 요청이나 SSH(원격 서버 접속) 같은 긴 연결이 끊길 수 있기 때문입니다. 오래 붙어 있어야 하는 원격 세션이라면 tmux나 mosh처럼 끊겨도 다시 붙을 수 있는 도구를 함께 쓰는 것이 좋습니다. 휴대폰의 데이터 사용량과 배터리 소모도 같이 챙겨야 합니다.

열#

덮개를 닫은 채 깨어 있는 노트북을 가방에 넣어도 될까요? 처음에는 통풍구만 막히지 않으면 괜찮지 않을까 생각했습니다. Apple이 MacBook Pro의 공기 흐름을 공식으로 설명한 자료는 찾지 못했지만, 알려진 바로는 바닥 쪽 측면 가장자리로 공기를 빨아들이고 힌지 쪽 뒤로 내보냅니다.

하지만 통풍구만으로는 부족합니다. 닫힌 가방 안에서는 내보낸 뜨거운 공기를 다시 빨아들이게 되어 식힐 공기 자체가 점점 데워집니다. 알루미늄 본체를 통해 빠져나가던 열도 가방 천에 갇힙니다. 다행히 macOS는 온도가 오르면 성능을 낮춰 기기를 보호하기 때문에, 실제로 겪는 피해는 고장보다 작업이 느려지고 본체가 뜨거워지고 배터리 수명이 줄어드는 쪽에 가깝습니다.

그래서 기준은 작업량입니다. 다운로드, SSH 세션 유지, API 응답을 기다리는 에이전트처럼 가벼운 작업이라면 잠깐 가방에 넣고 이동하는 정도는 대체로 괜찮습니다. 빌드, 로컬 모델 실행, 영상 인코딩처럼 무거운 작업이라면 꺼내서 들고 다니는 편이 낫습니다. 판단이 서지 않으면 가방에 넣고 10–20분 뒤에 꺼내 바닥을 만져 보면 됩니다. 뜨겁다면 그 작업은 가방 안에서 돌릴 작업이 아닙니다.

맺으며#

지금 필자는 상황에 따라 이렇게 나눠 씁니다.

상황쓰는 것
덮개를 열어 둔 채 긴 작업caffeinate -i <명령> (작업이 끝나면 함께 풀림)
화면도 계속 켜 둬야 할 때caffeinate -d
덮개를 닫고 이동해야 할 때출발 전 핫스팟 연결 후 awake

돌아보면 이번 일에서 품이 든 곳은 켜는 쪽이 아니라 되돌리는 쪽이었습니다. 자동 해제 타이머, 켜져 있을 때만 뜨는 표시, 재부팅 뒤 한 번의 확인, 이전 타이머의 정리, 실패하면 멈추는 제거 스크립트가 모두 그렇습니다. 시스템 설정을 바꾸는 도구를 만든다면, 켜는 기능보다 그 설정이 어떻게 다시 꺼지는지를 먼저 세어 보는 것이 좋겠습니다.

참고 자료#