[HIGH] - 로그인 잠금 XFF 스푸핑 우회: X-Forwarded-For leftmost(위조 가능) → rightmost(Caddy가 붙인 실제 IP) 사용 - 멘토 기본 비밀번호 하드코딩 제거: env 미설정 시 SecureRandom 생성 후 로그 1회 출력. compose에 MENTOR_INITIAL_PASSWORD 추가 - 클릭 카드 키보드 접근 불가(학습문서 전체): Card에 role=button·tabIndex·Enter/Space·aria-label [MEDIUM] - CSV 수식 인젝션: =+-@ 로 시작하는 학생 자유입력 앞에 ' 붙여 엑셀 수식화 차단 (+회귀 테스트) - 학습문서 17개 <html lang=ko> 골격 주입 (스크린리더 한국어 발음) - 과제 제출 폼 label htmlFor/id 연결 - 상단 아이콘 링크(👤·🔑) aria-label + 이모지 aria-hidden - 멘토 제출물 표 행 키보드 접근(role/tabIndex/Enter·Space) - 배지 amber/teal 글자 대비 4.5:1 확보(테마별 --amber-text/--teal-text) [LOW] - 코드에디터 포커스 표시(:focus-within), 문제번호 색 대비(--muted), 카드 :focus-visible - 폼 오류 메시지 role=alert (스크린리더 즉시 안내) 검증: 테스트 26개 통과, 운영에서 XFF 우회 차단(6회째 429)·카드 role/aria·문서 lang=ko 확인. PMD 29건은 전부 저심각 스멜로 실질 결함 없음. 각 수정에 원리 학습 주석. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
91 lines
3.2 KiB
YAML
91 lines
3.2 KiB
YAML
# 운영 배포용 컴포즈 — EC2에서 이 파일 하나로 전체 스택이 뜹니다.
|
|
# 사용법: cd deploy && cp .env.example .env (비밀번호 수정) && docker compose -f docker-compose.prod.yml up -d --build
|
|
# 학습 포인트: 개발(로컬)과 운영의 차이 —
|
|
# - DB 포트를 외부에 안 엽니다(컨테이너끼리 내부 네트워크로만 통신)
|
|
# - 비밀번호는 .env 파일로 주입 (코드/이미지에 안 넣음)
|
|
# - 재부팅해도 자동 시작(restart: unless-stopped)
|
|
|
|
services:
|
|
postgres:
|
|
image: postgres:17-alpine
|
|
container_name: mirim-postgres
|
|
environment:
|
|
POSTGRES_DB: mirim
|
|
POSTGRES_USER: mirim
|
|
POSTGRES_PASSWORD: ${DB_PASSWORD:?deploy/.env에 DB_PASSWORD를 설정하세요}
|
|
volumes:
|
|
- pgdata:/var/lib/postgresql/data
|
|
restart: unless-stopped
|
|
healthcheck:
|
|
test: ["CMD-SHELL", "pg_isready -U mirim"]
|
|
interval: 10s
|
|
timeout: 5s
|
|
retries: 5
|
|
|
|
backend:
|
|
build: ../backend
|
|
container_name: mirim-backend
|
|
environment:
|
|
DB_HOST: postgres # 학습 포인트: 컨테이너끼리는 서비스 이름이 곧 주소예요
|
|
DB_PORT: 5432
|
|
DB_NAME: mirim
|
|
DB_USER: mirim
|
|
DB_PASSWORD: ${DB_PASSWORD}
|
|
COOKIE_SECURE: "true" # 운영은 HTTPS(Caddy)이므로 세션 쿠키를 HTTPS 전용으로
|
|
LOG_LEVEL: INFO # 운영 로그는 INFO
|
|
# 텔레그램 알림 (값은 .env에 — 비어 있으면 알림만 꺼지고 앱은 정상)
|
|
TELEGRAM_BOT_TOKEN: ${TELEGRAM_BOT_TOKEN:-}
|
|
TELEGRAM_CHAT_ID: ${TELEGRAM_CHAT_ID:-}
|
|
# 멘토 최초 비밀번호 (첫 부팅 시드용). 비우면 랜덤 생성돼 로그에 1회 출력된다.
|
|
MENTOR_INITIAL_PASSWORD: ${MENTOR_INITIAL_PASSWORD:-}
|
|
depends_on:
|
|
postgres:
|
|
condition: service_healthy
|
|
restart: unless-stopped
|
|
|
|
# 프론트 빌드 결과물을 공유 볼륨(webroot)에 복사해 주고 종료하는 일회성 컨테이너
|
|
frontend-build:
|
|
build: ../frontend
|
|
container_name: mirim-frontend-build
|
|
volumes:
|
|
- webroot:/out
|
|
restart: "no"
|
|
|
|
caddy:
|
|
image: caddy:2-alpine
|
|
container_name: mirim-caddy
|
|
ports:
|
|
- "80:80"
|
|
- "443:443"
|
|
volumes:
|
|
- ./Caddyfile:/etc/caddy/Caddyfile:ro
|
|
- webroot:/srv:ro
|
|
- caddy-data:/data # HTTPS 인증서 보관 (재시작해도 유지)
|
|
depends_on:
|
|
- backend
|
|
- frontend-build
|
|
restart: unless-stopped
|
|
|
|
# Gitea — 가벼운 사내 Git 서버. 학생들이 clone/commit/push를 연습하는 곳.
|
|
# 학습 포인트: GitHub 같은 서비스를 우리 서버 안에 직접 띄운 것. 웹 UI는 :3000.
|
|
gitea:
|
|
image: gitea/gitea:1.23
|
|
container_name: mirim-gitea
|
|
environment:
|
|
GITEA__server__ROOT_URL: http://${GIT_HOST:-edu.awesomedevapp.com}:3000/
|
|
GITEA__server__SSH_PORT: "222"
|
|
GITEA__service__DISABLE_REGISTRATION: "false" # 학생 스스로 가입 가능
|
|
GITEA__security__INSTALL_LOCK: "true" # 웹 설치 마법사 건너뛰기(설정은 env로 완료)
|
|
ports:
|
|
- "3000:3000" # 웹 UI + HTTP clone/push
|
|
- "222:22" # SSH clone/push (선택)
|
|
volumes:
|
|
- gitea-data:/data
|
|
restart: unless-stopped
|
|
|
|
volumes:
|
|
pgdata:
|
|
webroot:
|
|
caddy-data:
|
|
gitea-data:
|