소켓 프로토콜
서버 소켓의 JSON 프로토콜이에요. 서버 사이에 세션을 옮기는 부분도 있어요.
다른 프로그램에서: 소켓
서버 소켓(~/.zeropty/mux.sock, ZEROPTY_SOCK으로 바꿔요)에 JSON 한 줄을 보내면 한 줄로 답해요. 연결 하나로 여러 명령을 보낼 수 있고, 답은 순서대로 와요. 소켓은 0600 권한이라 같은 사용자(와 root)만 접속할 수 있어요.
→ {"argv":["capture-pane","-p","-t","=work:"],"cwd":"/"}
← {"code":0,"out":"...화면...","err":""}cwd는 상대 경로(new-session -c,load-buffer)의 기준 폴더예요.- 요청에
"raw":true를 넣으면 답이 JSON 대신<code> <out 바이트 수> <err 바이트 수>한 줄과 그 뒤의 out, err 바이트 그대로 와요. JSON을 풀기 어려운 클라이언트(zsh 함수)용이에요. attach연결은 첫 답 뒤로 프레임을 주고받아요:[종류 u8][길이 u32 LE][바이트]. 서버 → 클라이언트는 0(출력), 1(세션 끝), 클라이언트 → 서버는 0(키), 1(크기: 열·행 u16 LE), 2(떼기)예요.- 첫 요청이
export-sessions나import-sessions인 연결은 세션을 넘기는 데 써요(내부용): 소켓 프로토콜.
sh
# 아무 언어에서나: 예를 들어 nc로
printf '{"argv":["list-sessions"],"cwd":"/"}\n' | nc -U ~/.zeropty/mux.sock세션 옮기기
세션을 다른 프로세스로 옮겨요. 실행 중인 프로그램은 그대로예요(pid도 같아요). 화면·스크롤백은 바이트로, 세션의 fd는 fd 전달(SCM_RIGHTS)로 넘겨요.
sh
zeropty pull-sessions -s ~/.zeropty/other.sock [이름...] # 다른 서버의 세션을 이 서버로 (이름 없으면 전부)
zeropty push-sessions -s ~/.zeropty/other.sock [이름...] # 이 서버의 세션을 다른 서버로
zeropty restart-server # 새 서버가 세션을 전부 넘겨받고 소켓 자리를 차지- 임베디드 엔진에서도 같은 명령이 돼요. 예를 들어 Node 앱이 시작할 때 서버의 세션을
pull-sessions로 가져오고, 끝날 때push-sessions로 돌려주면 앱을 재시작해도 세션이 안 끊겨요. restart-server는 지금 실행한zeropty바이너리로 새 서버를 띄워요. 그래서 세션을 잃지 않고 업그레이드하는 방법이기도 해요. 전환하는 몇 ms 사이에 들어온 명령은 옛 서버로 가요.- 받는 쪽이 "받았다"고 답한 세션만 보낸 쪽이 놓아요. 중간에 실패하면 세션은 원래 쪽에 그대로 있어요. 이름이 이미 있는 세션은 옮기지 않아요.
- 한계: 옮기는 순간 이스케이프 시퀀스가 반쯤 들어와 있었다면, 그 나머지는 글자로 찍혀요.
소켓 프로토콜: export-sessions, import-sessions
pull-sessions, push-sessions, restart-server가 서버 소켓에서 쓰는 내부용 요청이에요. 직접 보낼 일은 거의 없고, 세션 레코드 형식은 버전마다 바뀔 수 있어요(첫 바이트가 레코드 버전이라, 다른 버전의 레코드는 session record of another version으로 거절해요). 사람이 읽을 레퍼런스로 남겨 둬요. 구현: src/engine/handover.rs.
- 연결의 첫 요청 줄이
{"argv":["export-sessions", 이름...],"cwd":""}또는{"argv":["import-sessions"],"cwd":""}이면, 그 연결은 세션을 넘기는 데만 쓰고 끝나면 닫혀요. 그동안 서버는 다른 일을 하지 않아요(보통 몇 ms). - 양쪽 모두 한 단계에 10초까지 기다려요.
- 세션 하나는
[레코드 길이 u32 LE][레코드]로 가요. 세션의 fd(입력, 출력 중 열려 있는 것, 0~2개)는 길이 4바이트에 SCM_RIGHTS로 붙여 보내요. 길이 0이 끝 표시예요. 레코드는 256MB를 넘을 수 없어요. - 나머지 줄은 모두 JSON 한 줄이에요.
export-sessions (가져가는 쪽 → 서버)
→ {"argv":["export-sessions","a","b"],"cwd":""} 이름이 없으면 전부
← [길이][레코드] ... [0] 세션마다 fd와 함께
← ["b"] 서버에 없는 이름
→ {"taken":["a"],"failed":[]} 받은 세션. 서버는 이것만 놓아요
import-sessions (주는 쪽 → 서버)
→ {"argv":["import-sessions"],"cwd":""}
← "ready"
→ [길이][레코드] ... [0]
← {"taken":["a"],"failed":["duplicate session: b\n"]} 주는 쪽은 taken만 놓아요failed에는 받지 못한 이유가 들어가요(같은 이름이 이미 있음, 레코드 버전이 다름 등). 어느 단계에서든 연결이 끊기거나 형식이 틀리면 받은 쪽이 받은 세션을 놓고, 세션은 원래 쪽에 남아요.