website: dbclient 사고 이후 DB 조회용 GUI 뷰어를 별도 경로로 추가하기
dbclient 사고를 정리하다가, CD가 stage/prod 구분 없이 서버의 같은 워킹 디렉터리를 공유한다는 걸 실측으로 확인했다. 그 여파로 진행 중이던 sqlite-web GUI 뷰어 작업도 dev에만 머지해두면 다음 prod 배포 때 조용히 사라질 수 있다는 걸 깨닫고, 이번엔 바로 main으로 승격했다.
발단은 단순했다. dbclient CLI로 DB를 보여주면 결과가 터미널 텍스트로만 나와서, 백엔드 개발자가 스키마나 데이터를 훑어보기엔 불편했다. 그래서 read-only 웹 GUI를 하나 붙이기로 했고 sqlite-web을 골랐다.
붙이는 김에 조회뿐 아니라 조작까지 한 도구로 통합할지 잠깐 고민했다. sqlite-web에 SQL 실행창을 추가해서 dbclient를 아예 대체하는 방법도 가능은 했다. 그런데 sqlite-web 같은 범용 GUI 도구는 "DML은 허용, DDL은 차단" 같은 문장 단위 구분을 못 한다 — 전체 read-only냐 read-write냐 둘 중 하나다. dbclient가 이미 dbclient-sqlite-guard.sh로 갖춰둔 DDL 차단 로직을 GUI에도 넣으려면 sqlite-web 자체를 포크해서 같은 로직을 또 심어야 하는데, 그러면 차단 로직이 bash wrapper와 포크한 Python 두 군데에 중복되고 한쪽만 고치고 한쪽을 놓치는 사고가 나기 쉽다. dbclient 사고를 막 겪은 직후라 이런 종류의 중복 위험은 특히 피하고 싶었다. 그래서 통합은 포기하고, GUI는 전체 read-only로만 열고 조작은 기존 dbclient 경로를 그대로 쓰기로 했다.
계정도 분리했다. dbtunnel이라는 신규 SSH 계정을 만들어 셸 없이(nologin), permitopen="127.0.0.1:8090"/"8091"로 포트 포워딩 대상만 그 두 포트로 제한했다. dbclient의 no-port-forwarding과는 정반대로 "포워딩만 되고 그 외엔 아무것도 안 되는" 계정이다. docker-compose에는 sqlite-web-stage(8090)·sqlite-web-prod(8091) 서비스를 추가했는데, -r 플래그로 앱 레벨 read-only를 걸고 볼륨도 :ro로 마운트해서 이중으로 막았다. 포트는 127.0.0.1에만 바인딩해서 공인 노출은 전혀 늘리지 않았다 — 서버 밖에서는 SSH 터널을 거쳐야만 닿는다.
화면은 tmux로 합쳤다. infra/db-dev-ui.sh stage를 실행하면 왼쪽 pane에 dbtunnel 계정으로 연 SSH 포트포워딩(브라우저 GUI용), 오른쪽 pane에 기존 dbclient CLI가 뜬다. 두 세션은 완전히 독립된 SSH 인증이라 한쪽이 다른 쪽을 거치거나 막지 않는다. tmux가 없는 환경(Windows)을 위해 wt.exe 분할로 대체하는 경로도 넣었고, 둘 다 없으면 각 명령을 안내만 하고 끝나게 했다.
마지막으로 이 변경을 dev에만 두면 안 된다는 걸 명시적으로 남겼다. CD의 git checkout -f $TARGET_BRANCH가 stage/prod 구분 없이 서버의 같은 워킹 디렉터리를 공유한다는 걸 dbclient 사고에서 이미 실측했기 때문에, docker-compose.yml 변경을 dev에만 머지하면 다음 main 배포 때 이 서비스 정의가 없는 예전 파일로 조용히 되돌아갈 수 있다. 그래서 이번엔 dev 머지 직후 바로 main으로 승격하는 PR을 올렸다 — 이 커밋이 그 승격 병합이다. 서버에서 dbtunnel 계정을 만들고 컨테이너를 띄우는 건 여전히 사람이 직접 해야 하는 일로 남겨뒀다 — 공유 서버에 새 SSH 접근 수단을 추가하는 작업이라 자동화하지 않기로 했다.