KKC Labs포트폴리오홈 모니터링 PWA

라이브 영상은 클라우드에
올리지 않는다

실내 카메라를 약 0.6초 지연(수신 측 실측)으로 보고, 소리 이벤트를 감지해 알리고, 육아 기록을 남기는 개인용 PWA. 영상과 기록을 외부 서버에 최소한으로만 둔다.

유형개인 프로젝트 · 홈 모니터링 PWA
역할기획 · 설계 · 개발 · 운영 (KC)
공개비공개 (개인용)
스택
Next.jsCloudflare PagesWebAuthn 패스키go2rtc / WebRTCONNX 음향 분류

// the brief

보는 것과 알아채는 것은 다르다

실시간 영상을 볼 수 있다는 것과, 못 보고 있을 때 이상을 알아채는 것은 전혀 다른 문제다. 시판 제품은 대개 영상을 업체 클라우드로 올리고, 알림도 그 클라우드가 살아 있어야 온다. 영상이 남의 서버에 상시로 놓이는 것도, 그 서버가 죽으면 알림까지 같이 죽는 구조도 받아들이기 어려웠다.

그래서 목표를 둘로 잡았다. 라이브 영상은 클라우드에 올리지 않는다. 그리고 못 보고 있을 때는 별도 경로로 알린다. 이 둘을 이미 쓰던 육아 기록 앱에 더했다.

// approach

기록과 모니터링을 한 앱에

Next.js로 만든 PWA를 Cloudflare Pages에 올려 폰에서 앱처럼 쓰도록 했다. 주 사용처가 휴대폰이라 화면은 모바일 우선으로 설계하고, 기록은 몇 번의 탭으로 끝나게 다듬었다.

개발은 AI 도구를 엮어 빠르게 진행했다. 코드 생성과 리팩터의 속도는 AI에서 얻되, 무엇을 남기고 어떤 흐름으로 묶을지, 어디까지 신뢰할지는 사람이 판단하고 마감했다. 그것이 1인 스튜디오가 이만한 범위를 감당하는 방식이다.

육아 기록 앱으로 시작해 카메라를 붙여 '기록 + 안전망'으로 확장했다. 손으로 남기는 기록과 실시간 모니터링이라는 성격이 전혀 다른 기능을, 한 앱 안에서 자연스럽게 오가도록 묶는 것이 설계의 핵심이었다.

AI로 빠르게 짓되, 무엇을 남기고 어디까지 믿을지는 사람이 정했다.

// key decisions

실력이 갈린 세 결정

01

라이브 영상은 클라우드에 올리지 않는다

IP 카메라는 내부망에서만 접근하도록 두고, 같은 망의 맥 한 대가 RTSP를 저지연 WebRTC로 다시 뿌린다. 밖에서 볼 때도 공개 서버가 아니라 Tailscale 사설망만 지난다. 지연과 프라이버시 어느 쪽도 타협할 수 없어 엣지 처리를 택했다.

02

'못 볼 때'를 위한 별도 신뢰 경로

라이브뷰가 되는 것과, 문제가 생겼을 때 알림이 오는 것은 다른 문제다. 엣지가 죽거나 네트워크가 끊기면 그 사실 자체를 critical 푸시로 알리고, watchdog 프로세스가 파이프라인을 상시 감시한다. 감시가 아니라 '놓쳤을 때 알려주는' 보조 알림으로 설계했다.

03

개인 데이터에 맞는 인증·보안

남에게 새면 안 되는 데이터라, 로그인은 PIN과 WebAuthn 패스키로 두고 서버는 CSP 보안헤더·로그인 rate limit·import 검증·XSS 가드로 감쌌다. 편의 기능이 아니라 전제에서 출발한 결정이다.

세 결정을 관통하는 원칙은 하나다. 영상과 개인 기록을 외부 서버에 최소한으로만 둔다. 그래서 무거워 보이는 엣지 처리·패스키·사설망을 개인 프로젝트에도 그대로 적용했다.

// edge pipeline

카메라에서 폰까지, 클라우드에 올리지 않는 경로

맥 한 대가 카메라에 붙는 유일한 장치다. 카메라 쪽은 launchd로 상시 네 프로세스가 돈다. 영상을 재발행하는 go2rtc, 카메라 연결을 늘 열어 두는 keep-warm, 파이프라인 생존을 감시하는 watchdog, 소리를 판별하는 리스너. 카메라의 RTSP를 저지연 WebRTC로 바꿔 폰 화면에 띄우고, 바깥에서는 Tailscale 사설망으로만 닿는다. 소리 이벤트는 엣지에서 ONNX 음향 분류 모델로 판별하고, 밖으로는 점수만 보낸다. 판별한 소리는 어디에도 저장하지 않는다. 엣지가 끊기면 그 사실을 critical 푸시로 알리고, 이런 이벤트는 밤사이 요약으로 묶인다. 다만 이것은 보조 알림일 뿐 사람의 직접 확인을 대체하는 감시 장치가 아니다. 이 선은 코드와 앱 안내 모두에 분명히 적어 두었다.

RTSP 입력go2rtc → WebRTCTailscale 사설망ONNX 소리 판별launchd × 4

// kiosk

카메라와 실내 환경, 가전을 한 화면에

늘 켜 둔 태블릿을 키오스크로 삼아, 카메라 화면과 함께 날씨·미세먼지, 실내 온습도, 조명과 계절 가전 제어를 한 화면에 모았다. 기능을 늘어놓기보다 어디서 처리하고, 언제 알리고, 무엇을 보여 줄지를 정하는 데 공을 들였다.

제어와 날씨는 집 안의 맥이 먼저 맡는다. 가전 제어를 외부 클라우드 API에서 떼어 내, 카메라와 같은 맥이 집 안에서 MQTT로 직접 명령을 내리게 바꿨다. 로그인과 권한 확인은 클라우드가 하고, 맥은 그 결과로 발급된 12시간짜리 서명 토큰만 확인한다. 키오스크는 이 토큰으로 사설망을 지나 맥에 바로 명령하고, 직통이 안 되면 클라우드를 거쳐 같은 맥으로 보낸다. 조명 버튼에서 손을 뗀 뒤 기기 상태가 돌아오기까지 실측 0.58초가 걸린다. 클라우드의 가까운 지점에서는 공공 데이터 API가 시간 초과로 응답하지 않는다. 그래서 날씨와 미세먼지도 이 맥이 집 안에서 대신 받아 온다.

알림은 할 일이 있을 때만 보낸다. 미세먼지가 나쁘다는 것만으로는 알리지 않고, 나쁨이 1시간 이어지는데 창문이 열려 있을 때만 앱으로 알린다. 온습도 알림은 재알림 간격을 둘로 나눴다. 온도 이탈은 급성이라 3시간, 겨울 건조처럼 하루 내내 이어지는 습도 이탈은 24시간이다.

화면은 계절을 따른다. 겨울 모드를 따로 만들지 않고 냉방 시즌 스위치 하나에 묶었다. 냉방을 끄면 에어컨 칸은 한 줄로 접히고, 그 자리에 전기장판 같은 난방 가전과 습도 칸이 들어온다. 꺼 둔 냉방은 더워져도 스스로 다시 켜지 않고 알림만 보낸다. 난방 가전에는 켜기와 끄기만 두었고, 끄는 시각은 기기가 가진 자체 타이머에 맡겼다.

태블릿 키오스크엣지 직통 → 클라우드 폴백MQTT 로컬 제어조건부 알림계절 연동 화면

// result

결과

매일 실사용 중이며, 코드 변경은 배포 스크립트 하나로 검사부터 빌드, 배포까지 이어진다. 흩어져 있던 기록과 실시간 모니터링을 한 곳에서 처리한다. 데모가 아니라 계속 쓰이면서 다듬어지는 앱이다.

<1초
카메라 라이브뷰 지연
30+화면
기록·모니터링 기능
67개
DB 스키마 마이그레이션
← 이전GYMKC

// 이런 걸 만들어 드립니다.