← 개발 로그 목록

website: 어드민 계정 첫 등록(시드) 절차를 RUNBOOK·인수인계 문서에 남기기

/ 3분 분량 / 개발 로그

오늘 김우진·안시현·장찬욱·신선우 네 명을 prod 관리자로 등록하다가, 이 흐름을 정리해둔 문서가 하나도 없다는 걸 알게 됐다. 결국 `AdminSeedRunner` 코드를 다시 열어서 읽어가며 확인해야 했고, 다음에 또 이런 일이 생기면 똑같이 헤맬 게 뻔해서 작업 끝나자마자 RUNBOOK.md와 handoff.md에 정리해뒀다.

관리자를 처음 등록할 때 admins 테이블에 직접 INSERT하면 안 된다. 정식 경로는 서버의 .env.prod에 있는 ADMIN_SEED_ADMINS 환경변수에 이메일:이름을 이어붙이고 backend-prod 컨테이너를 재기동하는 것이다. 앱이 기동될 때 AdminSeedRunner가 이 값을 읽어서 계정을 만드는 구조다. 이 자체는 코드를 보면 파악할 수 있는 내용인데, 문제는 실제로 운영하면서 겪은 함정이었다.

오늘 4명을 시드하고 나서, 비밀번호 재설정 링크의 도메인이 잘못 나가는 버그를 발견해 고치고 재배포했다. 당연히 새 메일이 다시 나갈 줄 알았는데 안 나갔다. 이유는 시드 로직이 existsByEmail로 이미 존재하는 이메일을 걸러내는 멱등 구조라서였다. 재기동할 때마다 중복 생성되지 않는 건 좋은 설계인데, 그 역효과로 "이미 시드된 계정엔 코드를 고쳐도 새 메일이 자동으로 다시 안 나간다"는 걸 직접 겪고서야 알았다. 이건 코드만 읽어서는 바로 와닿지 않는 부분이라 RUNBOOK에 굵게 강조해서 남겼다.

이미 시드된 계정에 메일을 다시 보내야 할 때는 시드를 재트리거하는 게 아니라, 어드민 로그인 화면의 "비밀번호를 잊으셨나요" 흐름과 같은 공개 엔드포인트(AdminPasswordController)를 호출하면 된다는 것도 같이 적었다. 초기 비밀번호는 UUID.randomUUID()를 두 번 이어붙인 값이라 아무도 모르고, Admin 엔티티에 전화번호처럼 유추 가능한 필드도 없어서 재설정 메일이 사실상 유일한 로그인 경로라는 점도 짚어뒀다.

문서에 뭘 얼마나 담을지 고민한 지점도 있었다. 이 레포는 공개 레포라, 실제 prod 도메인을 찍은 복붙용 curl 명령이나 email_log 컬럼명을 나열한 SQL을 그대로 넣으면 보안상 좋지 않겠다는 판단이 들었다. 그래서 상세 요청 형식은 프론트 adminApi.ts와 백엔드 컨트롤러가 정본이라고만 가리키고, 발송 이력 확인은 db-access 스킬을 호출하라는 수준으로만 정리했다. 절차를 몰라서 헤매는 상황은 막되, 실제 인프라 정보는 노출하지 않는 선을 지키려 한 것이다.

작업 중에 SECURITY-POSTURE.md와 정체불명의 이미지 파일이 로컬에 걸려 있었는데, 이번 PR과 무관한 산출물이라 커밋에서 제외했다. 변경량 자체는 RUNBOOK.md 15줄, handoff.md 1줄로 크지 않지만, 실제로 겪은 함정을 그대로 기록해둔 덕에 다음에 비슷한 상황이 오면 코드를 다시 뒤질 필요 없이 이 문서만 보면 될 것 같다. PR은 생성하고 1분 만에 dev 브랜치로 머지됐다.