😤 또 다른 지옥의 시작…#
안녕하세요! Hugo 블로그 구축기 7편으로 돌아온 CoderRed입니다 😅
6편에서 텔레그램 알림 시스템을 구축했잖아요? 드디어 실시간으로 댓글 알림을 받을 수 있게 되었어요! 🎉
그런데… 여기서 끝이 아니었습니다. 😭
텔레그램으로 알림은 잘 오는데, ⚡ 댓글 승인 버튼을 누르면 계속 이런 에러가 떴어요:
Invalid Token
“뭐지? 방금 생성된 토큰인데 왜 무효라는 거야?” 🤔
결국 4시간의 지옥같은 디버깅 끝에 해결했는데… 정말 예상치 못한 곳에 함정들이 숨어있더라고요.
오늘은 그 4시간 삽질의 전 과정을 솔직하게 공유해드릴게요!
🔥 1단계: JWT_SECRET 의심 (첫 번째 함정)#
처음엔 당연히 JWT 설정 문제라고 생각했어요.
“아, 컨테이너 재시작하면서 JWT_SECRET이 바뀌었나?”
시도 1: JWT_SECRET 재설정#
docker stop cusdis && docker rm cusdis
docker run -d \
--name cusdis \
--restart unless-stopped \
--network npm_default \
-p 3000:3000 \
-e USERNAME="admin" \
-e PASSWORD="비밀번호" \
-e JWT_SECRET="ofcourseistillloveyou" \
-e DB_URL="file:/data/db.sqlite" \
-e NEXTAUTH_URL="https://comments.coderred.com" \
-v ~/cusdis-data:/data \
djyde/cusdis:latest
브라우저 쿠키도 모두 삭제하고, 다시 로그인하고…
결과: 여전히 Invalid Token
😤
🔥 2단계: NextAuth.js의 진짜 함정 발견#
Cusdis 로그를 자세히 보니 이런 에러가 떴어요:
https://next-auth.js.org/errors#jwt_session_error
JWSVerificationFailed: signature verification failed
이때 깨달았어요. NextAuth.js는 JWT_SECRET과 별도로 NEXTAUTH_SECRET을 찾을 수 있다는 것!
시도 2: NEXTAUTH_SECRET 추가#
docker stop cusdis && docker rm cusdis
docker run -d \
--name cusdis \
--restart unless-stopped \
--network npm_default \
-p 3000:3000 \
-e USERNAME="admin" \
-e PASSWORD="비밀번호" \
-e JWT_SECRET="ofcourseistillloveyou" \
-e NEXTAUTH_SECRET="ofcourseistillloveyou" \
-e DB_URL="file:/data/db.sqlite" \
-e NEXTAUTH_URL="https://comments.coderred.com" \
-v ~/cusdis-data:/data \
djyde/cusdis:latest
핵심: JWT_SECRET과 NEXTAUTH_SECRET을 똑같은 값으로 설정!
브라우저 쿠키 삭제, 다시 로그인, 새 댓글 달기…
결과: 여전히 Invalid Token
😭😭
🔥 3단계: 충격적인 진실 - 엉뚱한 서버로!#
그때 텔레그램에서 받은 승인 링크를 자세히 봤어요:
https://cusdis.com/open/approve?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
어?! 이거 cusdis.com
으로 가고 있네? 😱
우리 서버 https://comments.coderred.com
이 아니라 공식 Cusdis 서비스로 가고 있었던 거예요!
당연히 우리 서버에서 생성한 토큰을 cusdis.com에서 검증할 수 없죠. 이게 Invalid Token
의 진짜 원인이었어요!
시도 3: CUSDIS_BASE_URL 추가#
“이건 Base URL 설정 문제구나!”
docker stop cusdis && docker rm cusdis
docker run -d \
--name cusdis \
--restart unless-stopped \
--network npm_default \
-p 3000:3000 \
-e USERNAME="admin" \
-e PASSWORD="비밀번호" \
-e JWT_SECRET="ofcourseistillloveyou" \
-e NEXTAUTH_SECRET="ofcourseistillloveyou" \
-e DB_URL="file:/data/db.sqlite" \
-e NEXTAUTH_URL="https://comments.coderred.com" \
-e CUSDIS_BASE_URL="https://comments.coderred.com" \
-v ~/cusdis-data:/data \
djyde/cusdis:latest
새 댓글 달아서 테스트…
결과: 여전히 https://cusdis.com/open/approve
로 감! 😤😤😤
🔥 4단계: 웹훅에서 URL 강제 교체#
“그럼 웹훅에서 직접 바꿔버리자!”
webhook-telegram.js 수정#
if (type === 'new_comment') {
const { approve_link, ...otherData } = data;
// 🔥 강제로 URL 교체!
const fixedApproveLink = approve_link.replace(
'https://cusdis.com/open/approve',
'https://comments.coderred.com/api/approve' // /api/approve로 시도
);
const message = `🔔 새 댓글 알림!
...
⚡ [댓글 승인](${fixedApproveLink})
...`;
}
웹훅 컨테이너 재빌드하고 테스트…
결과: 이제 https://comments.coderred.com/api/approve
로 가는데… 404 Not Found 😤😤😤😤
🔥 5단계: API 경로의 미스터리#
“404라니? API 경로가 틀렸나?”
디버깅 시작#
# Cusdis 로그 실시간 확인
docker logs cusdis --follow
승인 링크를 클릭했는데… 로그에 아무것도 안 뜸! 😱
이건 요청이 Cusdis까지 도달하지 않고 있다는 뜻이에요!
NPM 로그 확인#
# NPM 로그 확인
docker logs npm-app-1 --follow
여기서도 404 에러… 그럼 NPM에서 라우팅이 안 되고 있다는 뜻!
다른 경로들 시도#
혹시 API 경로가 다를 수도 있다고 생각해서:
// webhook-telegram.js에서 여러 경로 시도
const possiblePaths = [
'https://comments.coderred.com/api/approve', // 시도 1
'https://comments.coderred.com/open/approve', // 시도 2
'https://comments.coderred.com/approve', // 시도 3
'https://comments.coderred.com/api/comments/approve' // 시도 4
];
하나씩 테스트해보니…
🎉 6단계: 드디어 정답 발견!#
https://comments.coderred.com/open/approve?token=...
바로 이거였어요! /open/approve
가 정답이었습니다! 🎉
최종 웹훅 코드#
const express = require('express');
const axios = require('axios');
const app = express();
app.use(express.json());
const BOT_TOKEN = process.env.BOT_TOKEN;
const CHAT_ID = process.env.CHAT_ID;
app.post('/cusdis-telegram', async (req, res) => {
try {
const { type, data } = req.body;
if (type === 'new_comment') {
const {
by_nickname,
by_email,
content,
page_title,
page_id,
approve_link
} = data;
// 🎯 최종 해결책!
const fixedApproveLink = approve_link.replace(
'https://cusdis.com/open/approve',
'https://comments.coderred.com/open/approve' // 이게 정답!
);
const postUrl = `https://coderred.com/posts/${page_id}/`;
const message = `🔔 새 댓글 알림!
📝 **${page_title}**
👤 작성자: ${by_nickname}${by_email ? ` (${by_email})` : ''}
💬 댓글: ${content}
🔗 [포스트 보기](${postUrl})
⚡ [댓글 승인](${fixedApproveLink})
🎛️ [관리 대시보드](https://comments.coderred.com)`;
const telegramUrl = `https://api.telegram.org/bot${BOT_TOKEN}/sendMessage`;
await axios.post(telegramUrl, {
chat_id: CHAT_ID,
text: message,
parse_mode: 'Markdown',
disable_web_page_preview: false
});
console.log(`✅ 댓글 알림 전송 완료: ${by_nickname} → ${page_title}`);
}
res.status(200).send('OK');
} catch (error) {
console.error('❌ 텔레그램 알림 실패:', error);
res.status(500).send('Error');
}
});
app.listen(3001, '0.0.0.0', () => {
console.log('🤖 텔레그램 웹훅 서버 실행: http://0.0.0.0:3001');
});
🛠️ 최종 시스템 구성#
Cusdis 설정 (완전체)#
docker run -d \
--name cusdis \
--restart unless-stopped \
--network npm_default \
-p 3000:3000 \
-e USERNAME="admin" \
-e PASSWORD="비밀번호" \
-e JWT_SECRET="ofcourseistillloveyou" \
-e NEXTAUTH_SECRET="ofcourseistillloveyou" \
-e DB_URL="file:/data/db.sqlite" \
-e NEXTAUTH_URL="https://comments.coderred.com" \
-v ~/cusdis-data:/data \
djyde/cusdis:latest
웹훅 컨테이너 재빌드#
cd ~/telegram-webhook
# 컨테이너 재빌드
docker stop telegram-webhook && docker rm telegram-webhook
docker build -t telegram-webhook .
docker run -d \
--name telegram-webhook \
--restart unless-stopped \
--network npm_default \
-e BOT_TOKEN="봇토큰" \
-e CHAT_ID="채팅ID" \
telegram-webhook
🎯 드디어 성공!#
이제 정말로 완벽한 시스템이 완성되었어요:
📝 포스트 작성 → 📤 Git 푸시 → 🚀 자동 배포
↓
💬 독자 댓글 작성 → 📱 텔레그램 알림 → ⚡ 원클릭 승인 (성공!)
🧠 4시간 삽질에서 얻은 교훈들#
기술적 교훈#
NextAuth.js의 복잡성
- JWT_SECRET과 NEXTAUTH_SECRET을 모두 설정해야 할 수 있음
- 둘 다 동일한 값으로 설정하는 것이 안전
셀프 호스팅의 함정
- 공식 서비스 URL이 하드코딩되어 있을 수 있음
- Base URL 환경변수가 모든 곳에 적용되지 않을 수 있음
API 경로 추적의 중요성
/api/approve
vs/open/approve
같은 미묘한 차이- 로그를 통한 실제 요청 경로 확인이 필수
디버깅 순서의 중요성
- 브라우저 → NPM → 애플리케이션 순서로 단계별 확인
삽질 방지 교훈#
가정하지 말기
- “당연히 이럴 거야"라는 생각이 가장 위험
- 로그가 안 나오는 시스템일 수도 있으니 다른 방법으로 확인
단계별 격리 테스트
- 한 번에 여러 개를 바꾸지 말고 하나씩
- 각 단계의 결과를 명확히 확인
URL과 경로 주의깊게 관찰
- 어디로 요청이 가는지 항상 확인
- 브라우저 개발자 도구 Network 탭 활용
환경변수의 함정
- 설정했다고 반드시 적용되는 건 아님
- 실제 동작하는 방식을 코드로 확인
🔧 트러블슈팅 체크리스트#
다음에 비슷한 문제가 생기면 이 순서로 확인하세요:
1단계: 기본 확인#
# 컨테이너 상태
docker ps | grep -E "(cusdis|telegram)"
# 환경변수 확인
docker exec cusdis printenv | grep -E "(JWT|AUTH)"
2단계: 로그 확인#
# 실시간 로그 모니터링
docker logs cusdis --follow
docker logs telegram-webhook --follow
docker logs npm-app-1 --follow
3단계: URL 추적#
- 텔레그램 알림에서 받은 링크 URL 확인
- 브라우저 개발자 도구에서 실제 요청 경로 확인
- NPM 설정에서 라우팅 규칙 확인
4단계: 단계별 테스트#
# 직접 API 테스트
curl -I https://comments.coderred.com/open/approve
curl -I https://comments.coderred.com/api/approve
# 컨테이너 내부 테스트
docker exec cusdis curl -I localhost:3000/open/approve
5단계: 브라우저 초기화#
- 쿠키 완전 삭제
- 시크릿/프라이빗 모드로 테스트
- 다른 브라우저로 테스트
🚨 주요 에러 해결 가이드#
“Invalid Token” 에러#
- JWT_SECRET = NEXTAUTH_SECRET 확인
- 브라우저 쿠키 삭제
- 승인 링크 URL 도메인 확인 (cusdis.com vs comments.coderred.com)
- API 경로 확인 (/open/approve vs /api/approve)
“404 Not Found” 에러#
- NPM 로그에서 라우팅 에러 확인
- API 경로가 실제 존재하는지 확인
- Cusdis 컨테이너까지 요청이 도달하는지 확인
텔레그램 알림은 오는데 승인이 안 됨#
- 승인 링크 URL이 올바른 도메인인지 확인
- 웹훅에서 URL 교체 로직 확인
- 브라우저에서 링크 클릭 시 Network 탭 확인
🎉 마지막으로…#
4시간의 삽질이었지만, 덕분에 정말 많은 것을 배웠어요:
- NextAuth.js의 숨겨진 동작 방식
- 셀프 호스팅 서비스의 함정들
- 체계적인 디버깅 방법론
- URL과 API 경로 추적의 중요성
이제 정말로 완전 자동화된 블로그 시스템이 완성되었어요!
앞으로는 이런 삽질 없이 오로지 글쓰기에만 집중할 수 있을 것 같아요 😊
혹시 비슷한 문제로 고생하시는 분들께 도움이 되었으면 좋겠어요. 댓글로 경험 공유해주세요! (이제 실시간 알림이 와서 바로 답변드릴 수 있어요 😄)
다음 편에서는 정말로 기술 콘텐츠나 투자 이야기로 돌아올 예정입니다! 🚀