mirim-app/deploy/healthcheck.sh
AWESOMEDEV 0a4c769d00 feat: 고도화 배치 7건 — 보안헤더·다운감시·로그회전·개인정보파기·문서정합성
[보안]
- Caddy 보안 응답헤더(HSTS·X-Frame-Options:DENY·X-Content-Type-Options·Referrer-Policy) +
  Server 헤더 제거. :80 catch-all 제거로 HTTP→HTTPS 자동 리다이렉트 복원(평문 표면 제거)
[운영 안정성]
- 헬스체크 스크립트 + systemd 타이머(2분): 공개 URL 두드려 UP/DOWN 변화 시에만 텔레그램 알림
- Docker 로그 회전(json-file 10m×3, YAML 앵커 공통적용) — 디스크 풀로 인한 조용한 다운 방지
[개인정보]
- 학생 파기: DELETE /api/mentor/students/{id} (멘토 권한) → 자식6테이블 먼저·계정 나중,
  @Transactional. 동의화면이 약속한 '파기' 이행. 멘토 화면에 이름입력 확인 파기 버튼
  StudentPurgeService + 테스트3(삭제순서·멘토거부·404)
[문서 정합성 — 지금 쓰는 학생 대상]
- STUDY.md: 없는 파일 가리키던 코드지도 정정(controller/→web/, ChecklistPage→DashboardPage,
  DocumentsPage→DocsPage, SubmissionsPage→AssignmentsPage)
- README: '6주'→8주, 학습센터/퀴즈/코딩 소개 추가, 폴더구조·초기계정(멘토만 시드·랜덤비번) 현행화
- 퀴즈 만점 안내: 없는 '코스 완료' 버튼 대신 '[← 코스로] 돌아가 완료' 로 정정

검증: 테스트29 통과, 운영에서 헤더4종·308리다이렉트·헬스체크·파기 E2E(무인증401·멘토200·
자식전체0건) 확인. 나머지 13건은 티켓 풀 확장감으로 남김.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 14:58:00 +09:00

56 lines
3.0 KiB
Bash

#!/bin/bash
# 이 파일이 하는 일: 학습 플랫폼이 살아 있는지 밖에서 확인하고, 상태가 "바뀔 때만" 텔레그램으로 알린다.
# systemd 타이머가 2분마다 실행한다(mirim-health.timer).
#
# 학습 포인트 ① — 왜 "앱 밖에서" 감시하나?
# 백엔드가 죽으면 백엔드 스스로는 "나 죽었어요"를 못 보낸다(죽었으니까!).
# 그래서 감시는 반드시 앱 바깥의 별도 프로세스가 해야 한다. 여기선 호스트의 이 스크립트가
# 실제 사용자와 똑같이 공개 URL(HTTPS)을 두드려 본다 — 이게 되면 Caddy·백엔드·인증서가 다 정상.
#
# 학습 포인트 ② — "상태 변화"에만 알린다.
# 2분마다 "정상입니다"를 보내면 알림이 스팸이 돼 아무도 안 본다.
# 그래서 직전 상태를 파일에 적어 두고, UP→DOWN(장애 발생)과 DOWN→UP(복구)의
# "바뀌는 순간"에만 한 번씩 보낸다. 좋은 알림은 조용하다가 필요할 때만 운다.
set -uo pipefail # -e는 안 쓴다: curl 실패(=사이트 다운)는 "에러"가 아니라 우리가 감지하려는 "정상 흐름"이다.
# ── 설정 ──
URL="https://edu.awesomedevapp.com/"
STATE_FILE="/home/ec2-user/.mirim-health-state" # 직전 상태(UP/DOWN)를 기억
FAIL_THRESHOLD=2 # 연속 N회 실패해야 DOWN으로 본다(일시적 깜빡임 무시)
TIMEOUT=10
# 텔레그램 설정은 백엔드 .env에서 그대로 가져온다(같은 봇·같은 방).
ENV_FILE="/home/ec2-user/mirim-app/deploy/.env"
BOT_TOKEN="$(grep -E '^TELEGRAM_BOT_TOKEN=' "$ENV_FILE" 2>/dev/null | cut -d= -f2- || true)"
CHAT_ID="$(grep -E '^TELEGRAM_CHAT_ID=' "$ENV_FILE" 2>/dev/null | cut -d= -f2- || true)"
notify() {
# 봇 설정이 없으면 조용히 넘어간다(알림만 꺼지고 감시 자체는 계속).
[ -z "$BOT_TOKEN" ] || [ -z "$CHAT_ID" ] && return 0
curl -s -m "$TIMEOUT" -X POST "https://api.telegram.org/bot${BOT_TOKEN}/sendMessage" \
-H "Content-Type: application/json; charset=utf-8" \
--data-binary "$(printf '{"chat_id":%s,"text":%s}' "$CHAT_ID" "$(printf '%s' "$1" | python3 -c 'import json,sys; print(json.dumps(sys.stdin.read()))')")" >/dev/null || true
}
# ── 현재 상태 확인 ──
# 연속 FAIL_THRESHOLD회 실패해야 DOWN. 한 번이라도 성공하면 즉시 UP.
now="DOWN"
for i in $(seq 1 "$FAIL_THRESHOLD"); do
if code="$(curl -s -o /dev/null -m "$TIMEOUT" -w '%{http_code}' "$URL")" && [ "$code" = "200" ]; then
now="UP"
break
fi
sleep 3
done
# ── 직전 상태와 비교, 바뀌었을 때만 알림 ──
prev="$(cat "$STATE_FILE" 2>/dev/null || echo "UP")" # 첫 실행은 UP으로 가정(정상 시작 전제)
if [ "$now" != "$prev" ]; then
if [ "$now" = "DOWN" ]; then
notify "🔴 학습 플랫폼 응답 없음 — $URL 이 열리지 않습니다. 서버를 확인해 주세요. (연속 ${FAIL_THRESHOLD}회 실패)"
else
notify "🟢 학습 플랫폼 정상 복구 — $URL 이 다시 열립니다."
fi
echo "$now" > "$STATE_FILE"
fi