From 9b530da6e10c10e138e1cdec603028646067bcb0 Mon Sep 17 00:00:00 2001 From: ttipo Date: Tue, 3 Nov 2026 02:11:14 +0900 Subject: [PATCH] =?UTF-8?q?docs:=20=EB=B0=B1=EC=97=85=20=ED=82=A4=EB=8A=94?= =?UTF-8?q?=20=EB=B2=84=ED=82=B7=EC=9D=B4=20=EC=95=84=EB=8B=88=EB=9D=BC=20?= =?UTF-8?q?=EC=84=9C=EB=B8=8C=20=EA=B3=84=EC=A0=95=EC=97=90=EC=84=9C=20?= =?UTF-8?q?=E2=80=94=20Object=20Storage=20=EB=A7=8C=20=C2=B7=20=EB=B2=84?= =?UTF-8?q?=ED=82=B7=20=ED=95=98=EB=82=98=EB=A1=9C=EB=8A=94=20=EB=AA=BB=20?= =?UTF-8?q?=EC=A2=81=ED=9E=8C=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit NCP 버킷에는 따로 키가 없다(사용자가 버킷을 만든 뒤 키를 찾지 못했다). 키는 서브 계정의 Access Key 탭에서 만들고 Secret Key 는 그때만 보인다 — 차례를 README 에 적었다. "이 버킷만 쓰는 하위 계정"은 NCP 문서로 뒷받침되지 않는다(관리형 정책은 Object Storage 전체, 사용자 정의 정책에도 버킷을 고르는 칸이 문서에 없다) — README · ops.env 예시 · 체크리스트를 "Object Storage 만 쓰는 서브 계정 · 같은 계정에 다른 버킷을 두지 않는다"로 바로잡았다. Co-Authored-By: Claude Opus 5.5 --- .claude/rules/checklist.md | 2 +- mail/README.md | 8 ++++++-- ops/ops.env.example | 3 ++- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/.claude/rules/checklist.md b/.claude/rules/checklist.md index 55a8d7d..8885e49 100644 --- a/.claude/rules/checklist.md +++ b/.claude/rules/checklist.md @@ -17,7 +17,7 @@ 12단계부터 운영은 `MAIL_WEBHOOK_SECRET`(엔진 웹훅 서명 키 — 영문 · 숫자 32자 이상, `openssl rand -hex 24`)도 필수다 — setup 과 backend 가 같은 값을 읽는다 13단계부터 운영은 `DB_IMAGE` 를 예시의 고정값(판 · digest)으로 두고, 첫 setup 은 복구 관리자와 함께(`--recovery` — 관리자 허용 IP 를 처음 걸 때 되돌릴 길) 돌린다. 이미 운영 중인 서버는 mail/README "13단계 전부터 운영 중인 서버" 차례를 따른다 -- [ ] 운영 백업 · 감시(13단계 — 호스트 systemd, mail/README "백업 › 설치" · "감시"): Object Storage 비공개 버킷 + 그 버킷만 쓰는 하위 계정 키, restic 비밀번호(**서버 밖에도 보관** — 잃으면 백업을 풀 수 없다), +- [ ] 운영 백업 · 감시(13단계 — 호스트 systemd, mail/README "백업 › 설치" · "감시"): Object Storage 비공개 버킷 + Object Storage 만 쓰는 하위 계정 키(버킷에는 따로 키가 없고 NCP 는 키를 버킷 하나로 좁히지 못한다 — 같은 계정에 다른 버킷을 두지 않는다), restic 비밀번호(**서버 밖에도 보관** — 잃으면 백업을 풀 수 없다), `/etc/arasystem/ops.env`, 타이머 셋(백업 03:30 · 점검 15분 · 부팅 되살리기) 켜기, 첫 백업 뒤 복원 연습(`drill`) 한 번. Healthchecks 체크 둘 · UptimeRobot 감시 셋 — 알림은 우리 메일 서버가 아닌 주소로 - [ ] `backend/.env.{env}.example` → `backend/.env.{env}` 복사 후 `JWT_SECRET` 등 고유 시크릿 작성 (compose `env_file` 로 로드 — 파일이 없으면 기동 실패) - [ ] `flutter/.env.example` → `flutter/.env` 복사 후 `API_URL` 확인 (Android 에뮬레이터: `10.0.2.2`) diff --git a/mail/README.md b/mail/README.md index a802f0f..29ef43a 100644 --- a/mail/README.md +++ b/mail/README.md @@ -288,7 +288,7 @@ node --env-file=.env.development mail/scripts/setup.mjs - **계정 보안(12단계)** — `.env.production` 의 `MAIL_WEBHOOK_SECRET`(엔진 웹훅의 서명 키 — `openssl rand -hex 24` 로 만든 값, 사람이 지은 값은 쓰지 않는다). 운영 필수다 — 아래 2)의 `up` **전에** 넣는다(없으면 백엔드가 env 검사에서 멈춰 웹메일 전체가 서고, setup 도 멈춘다). 직원에게 미리 알린다 — 2단계 인증을 켜면 아웃룩 · 휴대폰 메일 앱은 앱 비밀번호로 다시 연결해야 한다 ([계정 보안](#계정-보안-12단계--인프라)) -- **전환 준비(13단계)** — 이미 떠 있는 서버는 [13단계 전부터 운영 중인 서버](#13단계-전부터-운영-중인-서버)의 차례대로(첫 setup 은 `--recovery`). 첫 직원을 옮기기 **전에** 백업과 15분 점검을 켠다: NCP Object Storage 버킷 · 그 버킷만 쓰는 하위 계정 키 · restic 비밀번호(비밀번호 관리자에 따로 보관), +- **전환 준비(13단계)** — 이미 떠 있는 서버는 [13단계 전부터 운영 중인 서버](#13단계-전부터-운영-중인-서버)의 차례대로(첫 setup 은 `--recovery`). 첫 직원을 옮기기 **전에** 백업과 15분 점검을 켠다: NCP Object Storage 비공개 버킷 · Object Storage 만 쓰는 하위 계정 키 · restic 비밀번호(비밀번호 관리자에 따로 보관), Healthchecks.io 점검 둘 · UptimeRobot 감시 셋 ([백업](#백업) · [감시](#감시-13단계)). `.env.production` 의 `DB_IMAGE` 를 예시와 같은 값(판 · digest 고정 — 옛 파일은 `postgres:15-alpine`)으로 바꾼다 — 다음 `up -d` 에서 db 가 다시 만들어진다(몇 초 · 데이터는 볼륨 그대로, 같은 메이저 15 라 덤프 · 복원은 필요 없다). frontend 는 `MAIL_DOMAIN` · `MAIL_HOSTNAME` 으로 [메일 프로그램 자동 설정](#메일-프로그램-자동-설정) 응답을 만든다 — 이미 있는 값이라 새로 넣을 것은 없다 @@ -1769,7 +1769,11 @@ sudo systemctl enable arasystem-mail-recover.service systemctl list-timers 'arasystem-*' ``` -- **NCP Object Storage** — 콘솔에서 한국 리전에 버킷을 만든다(공개 안 함 · 수명 주기 삭제 규칙 없음 — 지우기는 restic 이 한다). API 키는 이 버킷만 쓰는 서브 계정으로. +- **NCP Object Storage** — 콘솔에서 한국 리전에 버킷을 만든다(공개 안 함 · 수명 주기 삭제 규칙 없음 — 지우기는 restic 이 한다). + **버킷에는 따로 키가 없다** — API 인증키(Access Key ID · Secret Key)는 Object Storage 만 쓰는 **서브 계정**의 것을 쓴다(메인 계정 키는 서버 · 결제까지 연다): + 콘솔 › Sub Account › Sub Accounts › 서브 계정 생성(접근 유형은 **API Gateway 접근만** — 콘솔 로그인 없음) → 그 계정의 **정책** 탭에 관리형 `NCP_OBJECT_STORAGE_MANAGER` 하나 + (`…_VIEWER` 는 읽기만이라 백업이 안 된다) → **Access Key** 탭에서 추가 — **Secret Key 는 만들 때만 보인다**, 그 자리에서 비밀번호 관리자(restic 비밀번호와 같은 항목)에 둔다 → ops.env 의 `AWS_ACCESS_KEY_ID` · `AWS_SECRET_ACCESS_KEY`. + NCP 문서에는 키를 버킷 하나로 좁히는 방법이 없다(2026-11-03 — 사용자 정의 정책도 버킷을 고르는 칸이 문서에 없다) — 이 키로 같은 계정의 버킷이 모두 열리므로 다른 버킷을 두지 않는다(시험 버킷은 쓸모가 끝나면 지운다). 만들 때의 다른 칸은 기본값 그대로 — **잠금 해제**(보존 기간을 걸면 restic 이 지난 백업을 지우지 못해 prune 이 실패한다) · **암호화(KMS) 해제**(restic 이 이미 암호화한다. 한 번 켜면 끌 수 없다) · 타 계정 권한 없음. "공개"는 버킷의 폴더 · 파일 목록을 누구에게나 연다 — restic 은 키로 서명해 들어가므로 필요 없다(NCP 가이드 "버킷 관리", 2026-11-03 확인). `RESTIC_REPOSITORY=s3:https://kr.object.ncloudstorage.com/<버킷>/arasystem`, `RESTIC_OPTIONS='-o s3.region=kr-standard -o s3.bucket-lookup=path'` — diff --git a/ops/ops.env.example b/ops/ops.env.example index 17f0061..7713173 100644 --- a/ops/ops.env.example +++ b/ops/ops.env.example @@ -31,7 +31,8 @@ RESTIC_REPOSITORY=s3:https://kr.object.ncloudstorage.com/change-me-bucket/arasys # NCP 의 S3 옵션 — 지역 kr-standard(서명 V4 에 쓴다) · 경로 방식(https://<엔드포인트>/<버킷> — NCP 는 경로 · 가상 호스트 방식 둘 다 받는다) # (근거: NCP API 가이드 Object Storage — https://api.ncloud-docs.com/docs/storage-objectstorage · …-listobjects) RESTIC_OPTIONS='-o s3.region=kr-standard -o s3.bucket-lookup=path' -# NCP 서브 계정의 API 인증키 — 이 버킷만 쓰게 좁힌 계정으로 (콘솔 › Sub Account) +# NCP 서브 계정의 API 인증키 — Object Storage 만 쓰는 서브 계정으로 (콘솔 › Sub Account — 접근 유형 API Gateway 만 · 정책 NCP_OBJECT_STORAGE_MANAGER 하나 · Access Key 탭). +# 버킷에는 따로 키가 없고, NCP 는 키를 버킷 하나로 좁히지 못한다 — 같은 계정에 다른 버킷을 두지 않는다. Secret Key 는 만들 때만 보인다(바로 비밀번호 관리자에) AWS_ACCESS_KEY_ID=change-me AWS_SECRET_ACCESS_KEY=change-me # 보존 — 매일 7 · 매주 4 · 매달 6 (그보다 오래된 백업은 지운다 — 지운 메일도 이 기간까지만 백업에 남는다)