Skip to content

Commit 978e1c4

Browse files
ImTotemclaude
andcommitted
feat(infra): add full deployment infrastructure, security hardening, and CI/CD
- Add deploy guide, credential bug fix (volume mount, deploy.sh copy) - Certbot: webhook → Bot Token + Block Kit, channel separation (error/certbot) - Security: remove hardcoded JWT secret, cookie_secure=True default, secrets module for verify codes, disable /docs /redoc, sort_by whitelist validation - Add catch-all exception handler with Slack error notifications - Add PostgreSQL, Valkey, MongoDB to docker-compose.db.yml (KOSP-style bind mounts) - Add n8n, frontend upstream, multi-domain nginx (api/n8n/internal/stage) - CI/CD: credentials via SCP from bcsdlab-credentials repo, no server GitHub auth - nginx: gzip, HTTP/2, SSL session cache, OCSP stapling, HSTS, WebSocket support - Remove CERTBOT_EMAIL, env var defaults in infra files, uvicorn reload excludes Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 57d625e commit 978e1c4

16 files changed

Lines changed: 939 additions & 16 deletions

File tree

.env.example

Lines changed: 47 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,61 @@
1+
# === Application ===
12
# Google OAuth
23
GOOGLE_CLIENT_ID=your-google-client-id
3-
4-
# JWT
5-
JWT_SECRET=your-jwt-secret-key
6-
JWT_ALGORITHM=HS256
7-
JWT_EXPIRE_MINUTES=1440
8-
94
# Google Sheets
105
GOOGLE_SHEETS_ID=your-google-sheets-id
6+
# Google 서비스 계정 JSON 파일 경로 (프로젝트 루트 기준, deploy.sh가 bcsdlab-credentials/bcsd-api/backend/에서 복사)
117
GOOGLE_SERVICE_ACCOUNT_FILE=credentials.json
128

139
# Resend (학교 이메일 인증)
1410
RESEND_API_KEY=re_your_api_key
1511
RESEND_SENDER=onboarding@resend.dev
1612

17-
# CORS (쉼표로 여러 origin 구분)
13+
# JWT
14+
JWT_SECRET=your-jwt-secret-key
15+
JWT_ALGORITHM=HS256
16+
JWT_EXPIRE_MINUTES=1440
17+
18+
# SECURITY
1819
CORS_ORIGINS=http://localhost:3000
20+
COOKIE_NAME=access_token
21+
# 로컬 개발 시 false, 프로덕션은 true (기본값: true)
22+
# 웹브라우저와 웹서버가 HTTPS로 통신하는 경우에만 웹브라우저가 쿠키를 서버로 전송하는 옵션
23+
COOKIE_SECURE=false
24+
25+
# === Database ===
26+
POSTGRES_USER=bcsd
27+
POSTGRES_PASSWORD=change-me
28+
POSTGRES_DB=bcsd
29+
POSTGRES_PORT=5432
30+
POSTGRES_VOLUME_PATH=/home/ubuntu/bcsd-data/postgres
1931

32+
REDIS_PASSWORD=change-me
33+
REDIS_PORT=6379
34+
REDIS_VOLUME_PATH=/home/ubuntu/bcsd-data/redis
35+
36+
MONGO_USER=bcsd
37+
MONGO_PASSWORD=change-me
38+
MONGO_PORT=27017
39+
MONGO_VOLUME_PATH=/home/ubuntu/bcsd-data/mongo
40+
41+
# === Infrastructure ===
2042
# SpiceDB (권한 관리)
21-
SPICEDB_ENDPOINT=localhost:50051
43+
SPICEDB_ENDPOINT=spicedb:50051
2244
SPICEDB_TOKEN=bcsd-dev-token
45+
DOMAIN=api.bcsdlab.com
46+
N8N_DOMAIN=n8n.bcsdlab.com
47+
FRONTEND_DOMAIN=stage.bcsdlab.com
48+
49+
# === n8n ===
50+
N8N_AUTH_USER=admin
51+
N8N_AUTH_PASSWORD=change-me
52+
N8N_ENCRYPTION_KEY=change-me-random-string
53+
54+
# === Notifications (Slack Bot) ===
55+
SLACK_BOT_TOKEN=xoxb-your-bot-token
56+
# API 에러 알림 채널
57+
SLACK_ERROR_CHANNEL=C0XXXXXXXXX
58+
# Certbot 갱신 알림 채널
59+
SLACK_CERTBOT_CHANNEL=C0YYYYYYYYY
60+
# Certbot 메시지 prefix
61+
SLACK_SERVER_NAME=*[인터널]*

.github/workflows/ci-cd.yml

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
name: CI/CD
2+
3+
on:
4+
push:
5+
branches: [main]
6+
7+
concurrency:
8+
group: deploy-production
9+
cancel-in-progress: false
10+
11+
jobs:
12+
lint:
13+
runs-on: ubuntu-latest
14+
permissions:
15+
checks: write
16+
steps:
17+
- uses: actions/checkout@v4
18+
19+
- uses: actions/setup-python@v5
20+
with:
21+
python-version: "3.12"
22+
cache: "pip"
23+
cache-dependency-path: pyproject.toml
24+
25+
- run: pip install -e ".[dev]" pyright
26+
- run: pyright src/
27+
28+
test:
29+
runs-on: ubuntu-latest
30+
permissions:
31+
checks: write
32+
steps:
33+
- uses: actions/checkout@v4
34+
35+
- uses: actions/setup-python@v5
36+
with:
37+
python-version: "3.12"
38+
cache: "pip"
39+
cache-dependency-path: pyproject.toml
40+
41+
- run: pip install -e ".[dev]"
42+
- run: pytest -v --junitxml=test-results.xml
43+
44+
- uses: EnricoMi/publish-unit-test-result-action@v2
45+
if: always()
46+
with:
47+
files: test-results.xml
48+
49+
deploy:
50+
needs: [lint, test]
51+
runs-on: ubuntu-latest
52+
permissions:
53+
contents: read
54+
steps:
55+
- name: Checkout credentials
56+
uses: actions/checkout@v4
57+
with:
58+
repository: BCSDLab/bcsdlab-credentials
59+
ssh-key: ${{ secrets.CONFIG_REPO_PEM_KEY }}
60+
path: credentials
61+
persist-credentials: false
62+
63+
- name: Upload credentials to server
64+
uses: appleboy/scp-action@v0.1.7
65+
with:
66+
host: ${{ secrets.SERVER_HOST }}
67+
username: ${{ secrets.SERVER_USER }}
68+
key: ${{ secrets.SERVER_SSH_KEY }}
69+
port: 22222
70+
source: "credentials/bcsd-api/backend/*"
71+
target: "~/BCSD_API"
72+
strip_components: 3
73+
74+
- name: Clean up credentials from runner
75+
if: always()
76+
run: rm -rf credentials
77+
78+
- name: Deploy
79+
uses: appleboy/ssh-action@v1
80+
with:
81+
host: ${{ secrets.SERVER_HOST }}
82+
username: ${{ secrets.SERVER_USER }}
83+
key: ${{ secrets.SERVER_SSH_KEY }}
84+
port: 22222
85+
script: |
86+
cd ~/BCSD_API
87+
git pull origin main
88+
bash infra/scripts/deploy.sh

docs/deploy-guide.md

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
# 초기 배포 가이드
2+
3+
## 사전 준비
4+
5+
### 서버 요구사항
6+
- Docker & Docker Compose (v2)
7+
- Git
8+
- SSH 접근 권한
9+
10+
### 필요 권한
11+
- `bcsdlab-credentials` 레포 접근 권한 (GitHub)
12+
- Google Cloud 서비스 계정 JSON 파일
13+
- (선택) Slack webhook URL
14+
15+
## 파일 배치
16+
17+
```
18+
~/bcsdlab-credentials/bcsd-api/backend/
19+
├── .env ← 환경변수 (모든 시크릿)
20+
├── credentials.json ← Google 서비스 계정 JSON
21+
└── ... ← 기타 의존 파일
22+
23+
~/BCSD_API/ ← 이 레포 clone
24+
├── .env ← deploy.sh가 복사
25+
├── credentials.json ← deploy.sh가 복사
26+
└── ... ← backend/ 내 모든 파일 복사됨
27+
```
28+
29+
`deploy.sh``bcsdlab-credentials/bcsd-api/backend/` 내 모든 파일을 프로젝트 루트로 복사합니다.
30+
Docker Compose가 `credentials.json`을 컨테이너 내부 `/app/credentials.json`에 read-only로 마운트합니다.
31+
32+
## 첫 배포 순서
33+
34+
### 1. 레포 clone
35+
36+
```bash
37+
cd ~
38+
git clone git@github.com:BCSDLab/BCSD_API.git
39+
cd BCSD_API
40+
```
41+
42+
### 2. credentials 레포 clone
43+
44+
```bash
45+
cd ~
46+
git clone git@github.com:BCSDLab/bcsdlab-credentials.git
47+
```
48+
49+
### 3. .env 작성
50+
51+
`bcsdlab-credentials/bcsd-api/backend/.env`를 작성합니다. `.env.example`을 참고하세요.
52+
53+
```bash
54+
cp BCSD_API/.env.example bcsdlab-credentials/bcsd-api/backend/.env
55+
# 각 값을 실제 값으로 수정
56+
```
57+
58+
### 4. credentials.json 배치
59+
60+
Google Cloud Console에서 서비스 계정 키(JSON)를 다운로드하여 배치합니다.
61+
62+
```bash
63+
# 다운로드한 JSON을 credentials 레포에 배치
64+
cp ~/Downloads/service-account-key.json ~/bcsdlab-credentials/bcsd-api/backend/credentials.json
65+
```
66+
67+
### 5. Docker 네트워크 생성
68+
69+
```bash
70+
docker network create bcsd
71+
```
72+
73+
### 6. SSL 초기화
74+
75+
```bash
76+
cd ~/BCSD_API
77+
bash infra/scripts/init-ssl.sh
78+
```
79+
80+
### 7. 첫 배포
81+
82+
```bash
83+
bash infra/scripts/deploy.sh
84+
```
85+
86+
이 스크립트가 수행하는 작업:
87+
1. `bcsdlab-credentials/bcsd-api/backend/` 내 모든 파일을 프로젝트 루트로 복사
88+
2. 새 슬롯(blue/green) 빌드 및 시작
89+
3. 헬스체크 (`/openapi.json`)
90+
4. nginx 트래픽 전환
91+
5. 이전 슬롯 정지
92+
93+
## 이후 배포
94+
95+
main push 시 CI/CD 자동 배포, 또는 수동:
96+
97+
```bash
98+
cd ~/BCSD_API
99+
bash infra/scripts/deploy.sh
100+
```
101+
102+
## 환경변수 목록
103+
104+
| 변수 | 용도 | 예시 |
105+
|------|------|------|
106+
| `GOOGLE_CLIENT_ID` | Google OAuth 클라이언트 ID | `123456.apps.googleusercontent.com` |
107+
| `JWT_SECRET` | JWT 서명 비밀키 | 랜덤 문자열 |
108+
| `JWT_ALGORITHM` | JWT 알고리즘 | `HS256` |
109+
| `JWT_EXPIRE_MINUTES` | JWT 만료 시간(분) | `1440` |
110+
| `GOOGLE_SHEETS_ID` | Google Sheets 문서 ID | Sheets URL의 `/d/` 뒤 값 |
111+
| `GOOGLE_SERVICE_ACCOUNT_FILE` | 서비스 계정 JSON 경로 | `credentials.json` |
112+
| `RESEND_API_KEY` | Resend 이메일 API 키 | `re_...` |
113+
| `RESEND_SENDER` | 발신 이메일 주소 | `noreply@bcsdlab.com` |
114+
| `CORS_ORIGINS` | 허용 CORS origin | `https://internal.bcsdlab.com` |
115+
| `COOKIE_NAME` | 인증 쿠키 이름 | `access_token` |
116+
| `COOKIE_SECURE` | Secure 쿠키 여부 | `true` (프로덕션) |
117+
| `POSTGRES_USER` | PostgreSQL 사용자 | `bcsd` |
118+
| `POSTGRES_PASSWORD` | PostgreSQL 비밀번호 | 랜덤 문자열 |
119+
| `POSTGRES_DB` | PostgreSQL 데이터베이스명 | `bcsd` |
120+
| `POSTGRES_PORT` | PostgreSQL 호스트 포트 | `5432` |
121+
| `POSTGRES_VOLUME_PATH` | PostgreSQL 데이터 경로 | `/home/ubuntu/bcsd-data/postgres` |
122+
| `REDIS_PASSWORD` | Redis 비밀번호 | 랜덤 문자열 |
123+
| `REDIS_PORT` | Redis 호스트 포트 | `6379` |
124+
| `REDIS_VOLUME_PATH` | Redis 데이터 경로 | `/home/ubuntu/bcsd-data/redis` |
125+
| `MONGO_USER` | MongoDB 사용자 | `bcsd` |
126+
| `MONGO_PASSWORD` | MongoDB 비밀번호 | 랜덤 문자열 |
127+
| `MONGO_PORT` | MongoDB 호스트 포트 | `27017` |
128+
| `MONGO_VOLUME_PATH` | MongoDB 데이터 경로 | `/home/ubuntu/bcsd-data/mongo` |
129+
| `SPICEDB_ENDPOINT` | SpiceDB gRPC 주소 | `spicedb:50051` |
130+
| `SPICEDB_TOKEN` | SpiceDB 인증 토큰 | 랜덤 문자열 |
131+
| `DOMAIN` | API 도메인 | `api.bcsdlab.com` |
132+
| `N8N_DOMAIN` | n8n 도메인 (선택) | `n8n.bcsdlab.com` |
133+
| `N8N_AUTH_USER` | n8n 기본 인증 사용자 | `admin` |
134+
| `N8N_AUTH_PASSWORD` | n8n 기본 인증 비밀번호 | 랜덤 문자열 |
135+
| `N8N_ENCRYPTION_KEY` | n8n credential 암호화 키 | 랜덤 문자열 |
136+
| `SLACK_BOT_TOKEN` | Slack Bot 토큰 (공유) | `xoxb-...` |
137+
| `SLACK_ERROR_CHANNEL` | API 에러 알림 채널 ID | `C0XXXXXXXXX` |
138+
| `SLACK_CERTBOT_CHANNEL` | Certbot 갱신 알림 채널 ID | `C0YYYYYYYYY` |
139+
| `SLACK_SERVER_NAME` | Certbot 메시지 prefix | `*[인터널]*` |
140+
141+
## 트러블슈팅
142+
143+
### 컨테이너 로그 확인
144+
145+
```bash
146+
cd ~/BCSD_API
147+
docker compose -f infra/docker/docker-compose.yml logs --tail=50 api-blue
148+
docker compose -f infra/docker/docker-compose.yml logs --tail=50 api-green
149+
```
150+
151+
### credentials.json FileNotFoundError
152+
153+
컨테이너 내부에 파일이 마운트되었는지 확인:
154+
155+
```bash
156+
docker compose -f infra/docker/docker-compose.yml exec api-blue ls -la /app/credentials.json
157+
```
158+
159+
프로젝트 루트에 `credentials.json`이 있는지 확인:
160+
161+
```bash
162+
ls -la ~/BCSD_API/credentials.json
163+
```
164+
165+
없으면 수동 복사:
166+
167+
```bash
168+
cp ~/bcsdlab-credentials/bcsd-api/backend/* ~/BCSD_API/
169+
```
170+
171+
### SSL 인증서 수동 갱신
172+
173+
```bash
174+
docker compose -f infra/docker/docker-compose.yml run --rm certbot certonly \
175+
--webroot -w /var/www/certbot \
176+
-d api.bcsdlab.com \
177+
--email admin@bcsdlab.com \
178+
--agree-tos --no-eff-email
179+
docker compose -f infra/docker/docker-compose.yml exec nginx nginx -s reload
180+
```
181+
182+
### docker network "bcsd" not found
183+
184+
```bash
185+
docker network create bcsd
186+
```

0 commit comments

Comments
 (0)