텔레그램 봇 개발 일지 #6 — 로컬 dashboard와 /restart
결론부터
봇은 백그라운드 process라 평소엔 보이지 않는다. 옆에 작은 HTTP 서버(DASHBOARD_PORT=8765, 기본 127.0.0.1)를 띄워서 현재 작업, 최근 도구 호출, 로그 tail을 브라우저로 실시간 볼 수 있게 했다. 봇 자체 상태가 망가졌을 때를 위한 /restart 슬래시 명령도 같이.
왜 dashboard가 필요한가
5편의 라이브 메시지는 사용자에게 응답 흐름을 보여준다. 하지만 그건 한 번에 한 turn만이다. 다음 같은 의문은 라이브 메시지로 답이 안 된다.
- 지금 어떤 토픽이 작업 중인가?
- 봇 process가 살아있는가, polling이 도는가?
- 최근 5분간 무슨 일이 있었나?
- 봇 시작 시점의 환경변수와 MCP 서버 목록은?
텔레그램 채팅으로 이걸 일일이 묻는 건 노이즈다. 브라우저에서 한 페이지로 다 보는 게 자연스럽다.
구조
작은 HTTP 서버(stdlib http.server 한 줄짜리 + asyncio task)로 띄운다. 별도 패키지 의존성 없음.
GET /— HTML 단일 페이지. 현재 작업, 토픽별 cwd, 최근 도구 호출 라인, MCP 서버 헬스, 환경변수 요약.GET /events— SSE(Server-Sent Events). 봇이 새 메시지를 받거나 도구를 호출할 때마다 푸시.GET /log— 로그 파일 tail.?lines=200으로 라인 수 조정.
async def emit(event: str, data: dict):
payload = f"event: {event}\ndata: {json.dumps(data)}\n\n"
for queue in subscribers:
await queue.put(payload)봇 메인 루프에서 메시지·도구 호출·세션 시작 같은 이벤트를 emit()으로 흘려보내면 브라우저가 즉시 업데이트된다.
/restart — 자가 재시작
봇이 자기 자신을 재시작해야 할 때(.env 변경, 코드 hotpath 수정, MCP 서버 추가)가 있다. 외부 터미널을 거치지 않고 텔레그램에서 /restart 한 줄로 끝내는 게 모바일에서 절실했다.
@whitelist_only
async def cmd_restart(update, context):
await update.message.reply_text("재시작합니다…")
# subprocess로 start.bat을 띄우고 현재 process는 종료
subprocess.Popen(["start.bat"], cwd=BOT_DIR, creationflags=DETACHED_PROCESS)
asyncio.get_event_loop().call_later(0.5, lambda: os._exit(0))자식 process를 detached로 띄우는 게 핵심이다. 부모(현재 봇) 종료 시 자식이 같이 죽지 않게 해야, 새 봇이 정상 부팅된 뒤 옛 봇이 깔끔하게 빠진다.
start.bat 경로
cwd=BOT_DIR을 명시 안 하면 텔레그램이 띄운 process의 cwd가 어디인지에 따라 start.bat을 못 찾는다. 절대 경로로 박는 게 안전.
LAN 노출과 토큰 가드
기본은 127.0.0.1에 묶어둔다 — 로컬 RDP 세션에서만 보면 충분한 경우. 핸드폰이나 LAN 안 다른 기기에서 보고 싶다면 DASHBOARD_HOST=0.0.0.0로 풀고, DASHBOARD_TOKEN을 32자 이상 시크릿으로 설정한다.
토큰이 있으면 모든 요청에 ?token=<value> 쿼리 또는 Authorization: Bearer <value> 헤더가 필요하다. 없으면 401. 토큰 미설정 + LAN 노출 조합은 봇의 모든 작업 로그가 동네 와이파이에 공개되는 것이라 위험.
DASHBOARD_DISABLED 가끔 dashboard 자체를 끄고 싶을 땐(포트 충돌, 보안 점검)
DASHBOARD_PORT=0 또는 DASHBOARD_DISABLED=1로 토글한다. 코드 수정 없이 환경변수 한 줄.
다음 편 예고
7편은 마무리 회고 — 6일에 걸쳐 만든 봇을 실제로 써보면서 느낀 점, 절약된 시간, 다시 만든다면 다르게 했을 결정들을 정리한다.