Commit Graph

9 Commits

Author SHA1 Message Date
ttipo 73babeaa2c feat(docker): 13단계 운영 기반 — 백업 · 감시 · 관리자 단단히 · 휴지통 30일 · 메일 프로그램 자동 설정
전 직원 전환 전에 서버가 스스로 지키고 되살릴 수 있게 했다.
- 관리자 단단히(setup): 관리자 역할에서 impersonate 를 빼고 관리자 허용 IP 를 앱 네트워크로 — 관리자 비밀번호로 바깥 993 · JMAP 에서 직원 메일함에 대리 로그인되던 길을 막았다.
  운영에서 처음 걸 때는 되돌릴 복구 관리자가 있을 때만 건다(관리자 잠김 방지).
- 휴지통 · 스팸 30일 정리를 KST 03:00 으로 명시. imapsync 로 옮긴 메일도 옮긴 때부터 센다(임시 엔진 실측).
- restic 백업(호스트 systemd 03:30 — 메일 볼륨 warm → 엔진 멈춤 9초 안팎 → final, pg_dump, 업로드, env) · 복원(새 서버 차례를 빈 클론에서 실측) · 15분 점검 · 부팅 되살리기, Healthchecks 하트비트.
- 메일 프로그램 자동 설정: frontend nginx 의 autoconfig · autodiscover 고정 응답(엔진 것은 POP3 를 알리고 입력을 되돌려 쓴다).
- 모든 서비스 docker 로그 보존, DB 이미지 판 · digest 고정.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 13:44:44 +09:00
ttipo d1ef0da488 feat(docker): 12단계 계정 보안 — 엔진 웹훅 · /api/internal 닫기 · 요청 대상 400
메일 프로그램(IMAP · SMTP) 로그인 기록을 엔진 웹훅으로 받는다.
- 루트 env MAIL_WEBHOOK_SECRET(setup · backend 공용, 운영 필수)과 backend 의 APP_NETWORK_SUBNET
- setup 이 엔진 웹훅을 맞추고(표시 머리글 — 소금 + 키 지문) 서명한 빈 묶음으로 받는 곳을 확인한다
  키를 바꾼 실행 · 게이트웨이 OTP · 테스트 직원의 402 를 따로 다룬다
- nginx 두 server 가 /api/internal/ 을 닫고, 요청 대상의 날 \(경로) · # · 0x80 이상은 location 전에 400
  Express 가 이런 대상을 다시 읽어 닫은 경로(/api/internal · /api/health)가 백엔드에서 열렸다(2026-10-31 리뷰)
- CI "닫는 경로 시험"(두 server × 세 경로의 변형) · 지시어만 세는 정적 검사
- mail/README: 웹훅 · 12단계 전부터 운영 중인 서버를 올리는 차례 · 휴대폰을 잃은 직원(관리 API 명령)
엔진 DNS 가 Custom(운영)이어도 웹훅은 backend 를 푼다 — 메일 컨테이너가 도커 DNS 를 쓴다(개발에서 확인).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 17:45:23 +09:00
ttipo 93147b88b7 feat(docker): 대용량 첨부 — ClamAV 검사기 · 공개 내려받기 호스트 · 받기 동시 연결 상한
- ClamAV 1.4.6 LTS 를 둘로 나눴다: clamd 는 백엔드와만 닿는 internal 네트워크(바깥 길 없음 · 서명 볼륨 읽기 전용),
  freshclam 은 자기 바깥 길에만. 비-root · cap_drop ALL. 운영은 늘 뜨고 개발은 profile clamav.
  clamd.conf 는 한 파일 한도 2047M 까지 끝까지 검사하고 암호 · 한도 초과를 판정으로 알린다(실측표는 mail/README).
  검사 중에 죽으면 남던 임시 파일을 띄울 때 지운다.
- nginx 를 포털 · 공개 내려받기(files.) 두 server 로 가른다(SHARE_PUBLIC_URL 과 CORS_ORIGIN 의 호스트 비교 — 같으면 포털이 함께 연다).
  조각 PUT 17m, 받기는 버퍼링 · 압축 끔 + X-Accel-Buffering 넘김 + 머리글 버퍼 16k,
  limit_conn 은 X-Forwarded-For 마지막 칸으로 IP 마다 공개 10 · 앱 안 30, 전체 공개 400 · 앱 안 200.
  받기 location 은 /api/ 블록 안의 대소문자를 가리지 않는 정규식이다(리뷰 — /FILES/ 같은 변형이 상한을 비켜 갔다).
  접근 로그는 경로 모양과 43자 토큰 모양으로 토큰을 가린다.
- 파일은 named volume <PROJECT_NAME>_files(개발도 — builder 단계가 /app/files 를 node 소유로 만든다), 백업하지 않는다.
- CI infra 잡: nginx 두 꼴 · 받기 경로 변형 시험(가짜 백엔드) · clamconf(-n 없이 기본값까지) · 틀린 SHARE_PUBLIC_URL 거부.
- mail/README: 악성 검사 · 공개 호스트 · NPM 칸(Advanced 다섯 줄) · 메모리 예산 · 문제 해결.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 00:37:21 +09:00
ttipo 458f12980d feat(docker): nginx 실시간 알림 경로 /api/mail-events
- 이 경로만 버퍼링 · 캐시 · 압축을 끄고 X-Accel-Buffering 을 앞으로 넘긴다(외부 nginx 계열 프록시도 이 응답만 버퍼링을 끈다)
- 읽기 사이 시간 제한 90s — 서버 ping 25초 · 화면 감시 75초보다 길고, 머리글이 오지 않으면 90초에 504 JSON
- 성공(200)도 접근 로그에 남긴다 — 연결이 끝날 때 한 줄이고, 시작된 스트림은 어떻게 끝나도 200 이라 rt= 로 끊김을 본다

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 13:32:57 +09:00
ttipo 5b6a97e5e1 feat(docker): 접근 로그에서 쿼리 빼기·Referrer-Policy strict-origin
검색어가 화면 주소(/mail/search?q=…)에도 들어가서 화면 요청과 Referer 까지 접근 로그에서 쿼리를 뺐다.
Referer 로 검색어가 실리지 않게 화면의 Referrer-Policy 를 strict-origin 으로 둔다. 성공한 1분 확인은 접근 로그를 남기지 않는다(폴링).
휴지통 영구 삭제는 엔진을 여러 번 기다리므로 발송·임시 저장과 같은 90s.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 23:56:10 +09:00
ttipo ff6f757fa4 feat(docker): 임시 저장 본문 4MB·/api 접근 로그에서 쿼리 빼기
임시 저장(POST·PUT /api/drafts)은 발송과 같은 본문이라 nginx 도 4MB·90초로 받는다(백엔드도 같은 파서).
/api 접근 로그는 쿼리 없이 남겨 메일 검색·받는 사람 추천의 검색어가 요청마다 남지 않게 한다.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 19:23:22 +09:00
ttipo 2a854073f7 feat(docker): 메일 발송 요청만 nginx 본문 한도 4MB
서식 본문과 긴 인용으로 발송 JSON 이 1MB 를 넘을 수 있다. 백엔드 파서와 같은 값으로 `location = /api/messages` 만
4m·90s 로 둔다 — 정확히 이 경로만이라 /api/messages/{id} 는 1m 그대로다.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 07:49:40 +09:00
ttipo 238d0ba838 chore(docker): 메일 엔진·네트워크 분리·같은 주소 배포와 Node 24 고정
웹메일 1단계를 개발 환경에서 띄우고 운영에 올리기 위한 인프라 구성이다.

- 메일 엔진 Stalwart(v0.16.27)와 개발용 Mailpit 을 compose profile `mail` 로 추가한다.
  초기 설정은 mail/scripts/setup.mjs 가 맡는다 — 여러 번 실행해도 안전하고,
  운영에서는 사전 검사가 실패하면 아무것도 바꾸지 않고 멈춘다
- 화면과 API 를 한 주소로 배포한다: 프론트 nginx 가 /api 를 백엔드로 넘기고,
  본문 한도·시간 제한·헤더 전달을 env 와 맞춘다. nginx 가 직접 내는 오류도 표준 실패 형식이다
- 인터넷을 향한 nginx 는 edge 네트워크로 백엔드에만 닿고, DB·메일 엔진은 app-network 에 둔다
  (메일 엔진의 신뢰 대역도 app-network 뿐)
- Node 24.22.1·npm 11.6.2·nginx 1.30.6 을 Dockerfile 과 CI 에 한 값으로 고정한다
- 개발 호스트 포트는 127.0.0.1 로 한정하고, data/ 는 클론 때부터 사용자 소유가 되게 .gitkeep 을 추적한다

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 01:31:21 +09:00
ttipo 18f10cece4 Initial commit 2026-10-26 12:28:48 +09:00